Merge "Merge "Merge "Merge "DO NOT MERGE: Bump CTS and CTS Verifier to 7.0_r18" into nougat-cts-dev am: 574ee81d02 -s ours" into nougat-mr1-cts-dev am: 65639e8ba8 -s ours" into oreo-cts-dev am: 55143364fc -s ours" into oc-dev am: 0c29e60748 am: 78f4674692 am: b9764176ab
am: 16b9a9edfa -s ours
Change-Id: I280918e30b752866388b9563ee3496802f11aa52
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
index 83e654e..3020548 100644
--- a/apps/CameraITS/pymodules/its/cv2image.py
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -12,27 +12,37 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import matplotlib
-matplotlib.use('Agg')
-
-import its.error
-from matplotlib import pylab
-import sys
-from PIL import Image
-import numpy
-import math
-import unittest
-import cStringIO
-import scipy.stats
-import copy
-import cv2
import os
+import unittest
+
+import cv2
+import its.caps
+import its.device
+import its.error
+import numpy
+
+VGA_HEIGHT = 480
+VGA_WIDTH = 640
+
def scale_img(img, scale=1.0):
"""Scale and image based on a real number scale factor."""
dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
+
+def gray_scale_img(img):
+ """Return gray scale version of image."""
+ if len(img.shape) == 2:
+ img_gray = img.copy()
+ elif len(img.shape) == 3:
+ if img.shape[2] == 1:
+ img_gray = img[:, :, 0].copy()
+ else:
+ img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
+ return img_gray
+
+
class Chart(object):
"""Definition for chart object.
@@ -57,6 +67,23 @@
self._scale_start = scale_start
self._scale_stop = scale_stop
self._scale_step = scale_step
+ self.xnorm, self.ynorm, self.wnorm, self.hnorm, self.scale = its.image.chart_located_per_argv()
+ if not self.xnorm:
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ if its.caps.read_3a(props):
+ self.locate(cam, props)
+ else:
+ print 'Chart locator skipped.'
+ self._set_scale_factors_to_one()
+
+ def _set_scale_factors_to_one(self):
+ """Set scale factors to 1.0 for skipped tests."""
+ self.wnorm = 1.0
+ self.hnorm = 1.0
+ self.xnorm = 0.0
+ self.ynorm = 0.0
+ self.scale = 1.0
def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
"""Take an image with s, e, & fd to find the chart location.
@@ -79,7 +106,7 @@
req['android.lens.focusDistance'] = fd
cap_chart = its.image.stationary_lens_cap(cam, req, fmt)
img_3a = its.image.convert_capture_to_rgb_image(cap_chart, props)
- img_3a = its.image.flip_mirror_img_per_argv(img_3a)
+ img_3a = its.image.rotate_img_per_argv(img_3a)
its.image.write_image(img_3a, 'af_scene.jpg')
template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
focal_l = cap_chart['metadata']['android.lens.focalLength']
@@ -95,41 +122,38 @@
print 'Chart/image scale factor = %.2f' % scale_factor
return template, img_3a, scale_factor
- def locate(self, cam, props, fmt, s, e, fd):
- """Find the chart in the image.
+ def locate(self, cam, props):
+ """Find the chart in the image, and append location to chart object.
- Args:
- cam: An open device session
- props: Properties of cam
- fmt: Image format for the capture
- s: Sensitivity for the AF request as defined in
- android.sensor.sensitivity
- e: Exposure time for the AF request as defined in
- android.sensor.exposureTime
- fd: float; autofocus lens position
-
- Returns:
+ The values appended are:
xnorm: float; [0, 1] left loc of chart in scene
ynorm: float; [0, 1] top loc of chart in scene
wnorm: float; [0, 1] width of chart in scene
hnorm: float; [0, 1] height of chart in scene
+ scale: float; scale factor to extract chart
+
+ Args:
+ cam: An open device session
+ props: Camera properties
"""
- chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
- s, e, fd)
+ if its.caps.read_3a(props):
+ s, e, _, _, fd = cam.do_3a(get_results=True)
+ fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+ chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
+ s, e, fd)
+ else:
+ print 'Chart locator skipped.'
+ self._set_scale_factors_to_one()
+ return
scale_start = self._scale_start * s_factor
scale_stop = self._scale_stop * s_factor
scale_step = self._scale_step * s_factor
+ self.scale = s_factor
max_match = []
# check for normalized image
if numpy.amax(scene) <= 1.0:
scene = (scene * 255.0).astype(numpy.uint8)
- if len(scene.shape) == 2:
- scene_gray = scene.copy()
- elif len(scene.shape) == 3:
- if scene.shape[2] == 1:
- scene_gray = scene[:, :, 0]
- else:
- scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
+ scene_gray = gray_scale_img(scene)
print 'Finding chart in scene...'
for scale in numpy.arange(scale_start, scale_stop, scale_step):
scene_scaled = scale_img(scene_gray, scale)
@@ -142,26 +166,33 @@
# determine if optimization results are valid
opt_values = [x[0] for x in max_match]
if 2.0*min(opt_values) > max(opt_values):
- estring = ('Unable to find chart in scene!\n'
+ estring = ('Warning: unable to find chart in scene!\n'
'Check camera distance and self-reported '
'pixel pitch, focal length and hyperfocal distance.')
- raise its.error.Error(estring)
- # find max and draw bbox
- match_index = max_match.index(max(max_match, key=lambda x: x[0]))
- scale = scale_start + scale_step * match_index
- print 'Optimum scale factor: %.3f' % scale
- top_left_scaled = max_match[match_index][1]
- h, w = chart.shape
- bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
- top_left = (int(top_left_scaled[0]/scale),
- int(top_left_scaled[1]/scale))
- bottom_right = (int(bottom_right_scaled[0]/scale),
- int(bottom_right_scaled[1]/scale))
- wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
- hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
- xnorm = float(top_left[0]) / scene.shape[1]
- ynorm = float(top_left[1]) / scene.shape[0]
- return xnorm, ynorm, wnorm, hnorm
+ print estring
+ self._set_scale_factors_to_one()
+ else:
+ if (max(opt_values) == opt_values[0] or
+ max(opt_values) == opt_values[len(opt_values)-1]):
+ estring = ('Warning: chart is at extreme range of locator '
+ 'check.\n')
+ print estring
+ # find max and draw bbox
+ match_index = max_match.index(max(max_match, key=lambda x: x[0]))
+ self.scale = scale_start + scale_step * match_index
+ print 'Optimum scale factor: %.3f' % self.scale
+ top_left_scaled = max_match[match_index][1]
+ h, w = chart.shape
+ bottom_right_scaled = (top_left_scaled[0] + w,
+ top_left_scaled[1] + h)
+ top_left = (int(top_left_scaled[0]/self.scale),
+ int(top_left_scaled[1]/self.scale))
+ bottom_right = (int(bottom_right_scaled[0]/self.scale),
+ int(bottom_right_scaled[1]/self.scale))
+ self.wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
+ self.hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
+ self.xnorm = float(top_left[0]) / scene.shape[1]
+ self.ynorm = float(top_left[1]) / scene.shape[0]
class __UnitTest(unittest.TestCase):
@@ -186,7 +217,8 @@
blur = cv2.blur(chart, (j, j))
blur = blur[:, :, numpy.newaxis]
sharpness[j] = (yuv_full_scale *
- its.image.compute_image_sharpness(blur / white_level))
+ its.image.compute_image_sharpness(blur /
+ white_level))
self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
numpy.sqrt(2), atol=0.1))
self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 26af162..66d28c2 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -207,7 +207,7 @@
'com.android.cts.verifier/.camera.its.ItsTestActivity '
'--activity-brought-to-front') % self.adb)
time.sleep(CMD_DELAY)
- _run(('%s shell am startservice --user 0 -t text/plain '
+ _run(('%s shell am start-foreground-service --user 0 -t text/plain '
'-a %s') % (self.adb, self.INTENT_START))
# Wait until the socket is ready to accept a connection.
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index 24b48bb..3fe7f15 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -646,7 +646,10 @@
ytile = math.ceil(ynorm * hfull)
wtile = math.floor(wnorm * wfull)
htile = math.floor(hnorm * hfull)
- return img[ytile:ytile+htile,xtile:xtile+wtile,:].copy()
+ if len(img.shape)==2:
+ return img[ytile:ytile+htile,xtile:xtile+wtile].copy()
+ else:
+ return img[ytile:ytile+htile,xtile:xtile+wtile,:].copy()
def compute_image_means(img):
@@ -697,6 +700,22 @@
return snr
+def compute_image_max_gradients(img):
+ """Calculate the maximum gradient of each color channel in the image.
+
+ Args:
+ img: Numpy float image array, with pixel values in [0,1].
+
+ Returns:
+ A list of gradient max values, one per color channel in the image.
+ """
+ grads = []
+ chans = img.shape[2]
+ for i in xrange(chans):
+ grads.append(numpy.amax(numpy.gradient(img[:, :, i])))
+ return grads
+
+
def write_image(img, fname, apply_gamma=False):
"""Save a float-3 numpy array image to a file.
@@ -780,6 +799,7 @@
[gy, gx] = numpy.gradient(luma)
return numpy.average(numpy.sqrt(gy*gy + gx*gx))
+
def normalize_img(img):
"""Normalize the image values to between 0 and 1.
@@ -790,21 +810,39 @@
"""
return (img - numpy.amin(img))/(numpy.amax(img) - numpy.amin(img))
-def flip_mirror_img_per_argv(img):
- """Flip/mirror an image if "flip" or "mirror" is in argv
+
+def chart_located_per_argv():
+ """Determine if chart already located outside of test.
+
+ If chart info provided, return location and size. If not, return None.
+
+ Args:
+ None
+ Returns:
+ chart_loc: float converted xnorm,ynorm,wnorm,hnorm,scale from argv text.
+ argv is of form 'chart_loc=0.45,0.45,0.1,0.1,1.0'
+ """
+ for s in sys.argv[1:]:
+ if s[:10] == "chart_loc=" and len(s) > 10:
+ chart_loc = s[10:].split(",")
+ return map(float, chart_loc)
+ return None, None, None, None, None
+
+
+def rotate_img_per_argv(img):
+ """Rotate an image 180 degrees if "rotate" is in argv
Args:
img: 2-D numpy array of image values
Returns:
- Flip/mirrored image
+ Rotated image
"""
img_out = img
- if "flip" in sys.argv:
- img_out = numpy.flipud(img_out)
- if "mirror" in sys.argv:
- img_out = numpy.fliplr(img_out)
+ if "rotate180" in sys.argv:
+ img_out = numpy.fliplr(numpy.flipud(img_out))
return img_out
+
def stationary_lens_cap(cam, req, fmt):
"""Take up to NUM_TRYS caps and save the 1st one with lens stationary.
@@ -829,6 +867,7 @@
raise its.error.Error('Cannot settle lens after %d trys!' % trys)
return cap[NUM_FRAMES-1]
+
class __UnitTest(unittest.TestCase):
"""Run a suite of unit tests on this module.
"""
diff --git a/apps/CameraITS/tests/inprog/test_test_patterns.py b/apps/CameraITS/tests/inprog/test_test_patterns.py
deleted file mode 100644
index f75b141..0000000
--- a/apps/CameraITS/tests/inprog/test_test_patterns.py
+++ /dev/null
@@ -1,41 +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
-
-def main():
- """Test sensor test patterns.
- """
- NAME = os.path.basename(__file__).split(".")[0]
-
- with its.device.ItsSession() as cam:
- caps = []
- for i in range(1,6):
- req = its.objects.manual_capture_request(100, 10*1000*1000)
- req['android.sensor.testPatternData'] = [40, 100, 160, 220]
- req['android.sensor.testPatternMode'] = i
-
- # Capture the shot twice, and use the second one, so the pattern
- # will have stabilized.
- caps = cam.do_capture([req]*2)
-
- img = its.image.convert_capture_to_rgb_image(caps[1])
- its.image.write_image(img, "%s_pattern=%d.jpg" % (NAME, i))
-
-if __name__ == '__main__':
- main()
-
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 752e02b..e78488e 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -90,8 +90,8 @@
pixel_pitch_w = (sensor_size["width"] / fmts[0]["width"] * 1E3)
print "Assert pixel_pitch WxH: %.2f um, %.2f um" % (pixel_pitch_w,
pixel_pitch_h)
- assert 1.0 <= pixel_pitch_w <= 10
- assert 1.0 <= pixel_pitch_h <= 10
+ assert 0.9 <= pixel_pitch_w <= 10
+ assert 0.9 <= pixel_pitch_h <= 10
assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
diag = math.sqrt(sensor_size["height"] ** 2 +
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index c3c2147..7594f9f 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -12,19 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.image
import its.caps
import its.device
+import its.image
import its.objects
import its.target
-def main():
- """Test that the android.sensor.sensitivity parameter is applied properly
- within a burst. Inspects the output metadata only (not the image data).
- """
+NUM_STEPS = 3
+ERROR_TOLERANCE = 0.97 # Allow ISO to be rounded down by 3%
- NUM_STEPS = 3
- ERROR_TOLERANCE = 0.97 # Allow ISO to be rounded down by 3%
+
+def main():
+ """Test android.sensor.sensitivity parameter applied properly in burst.
+
+ Inspects the output metadata only (not the image data).
+ """
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
@@ -35,15 +37,16 @@
sens_step = (sens_range[1] - sens_range[0]) / NUM_STEPS
sens_list = range(sens_range[0], sens_range[1], sens_step)
e = min(props['android.sensor.info.exposureTimeRange'])
- reqs = [its.objects.manual_capture_request(s,e) for s in sens_list]
- _,fmt = its.objects.get_fastest_manual_capture_settings(props)
+ reqs = [its.objects.manual_capture_request(s, e) for s in sens_list]
+ _, fmt = its.objects.get_fastest_manual_capture_settings(props)
caps = cam.do_capture(reqs, fmt)
- for i,cap in enumerate(caps):
+ for i, cap in enumerate(caps):
s_req = sens_list[i]
- s_res = cap["metadata"]["android.sensor.sensitivity"]
- assert(s_req >= s_res)
- assert(s_res/float(s_req) > ERROR_TOLERANCE)
+ s_res = cap['metadata']['android.sensor.sensitivity']
+ s_values = 's_write: %d, s_read: %d' % (s_req, s_res)
+ assert s_req >= s_res, s_values
+ assert s_res/float(s_req) > ERROR_TOLERANCE, ERROR_TOLERANCE
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
new file mode 100644
index 0000000..a1d9cb8
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -0,0 +1,174 @@
+# Copyright 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.
+
+import os
+
+import its.caps
+import its.device
+import its.image
+import its.objects
+import numpy as np
+
+NAME = os.path.basename(__file__).split('.')[0]
+PATTERNS = [1, 2]
+COLOR_BAR_ORDER = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED',
+ 'BLUE', 'BLACK']
+COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0],
+ 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1],
+ 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]}
+CH_TOL = 2E-3 # 1/2 DN in [0:1]
+LSFR_COEFFS = 0b100010000 # PN9
+
+
+def check_solid_color(cap, props):
+ """Simple test for solid color.
+
+ Args:
+ cap: capture element
+ props: capture properties
+ Returns:
+ True/False
+ """
+ print 'Checking solid TestPattern...'
+ r, gr, gb, b = its.image.convert_capture_to_planes(cap, props)
+ r_tile = its.image.get_image_patch(r, 0.0, 0.0, 1.0, 1.0)
+ gr_tile = its.image.get_image_patch(gr, 0.0, 0.0, 1.0, 1.0)
+ gb_tile = its.image.get_image_patch(gb, 0.0, 0.0, 1.0, 1.0)
+ b_tile = its.image.get_image_patch(b, 0.0, 0.0, 1.0, 1.0)
+ var_max = max(np.amax(r_tile), np.amax(gr_tile), np.amax(gb_tile),
+ np.amax(b_tile))
+ var_min = min(np.amin(r_tile), np.amin(gr_tile), np.amin(gb_tile),
+ np.amin(b_tile))
+ white_level = int(props['android.sensor.info.whiteLevel'])
+ print ' pixel min: %.f, pixel max: %.f' % (white_level*var_min,
+ white_level*var_max)
+ return np.isclose(var_max, var_min, atol=CH_TOL)
+
+
+def check_color_bars(cap, props, mirror=False):
+ """Test image for color bars.
+
+ Compute avg of bars and compare to ideal
+
+ Args:
+ cap: capture element
+ props: capture properties
+ mirror (bool): whether to mirror image or not
+ Returns:
+ True/False
+ """
+ print 'Checking color bar TestPattern...'
+ delta = 0.0005
+ num_bars = len(COLOR_BAR_ORDER)
+ color_match = []
+ img = its.image.convert_capture_to_rgb_image(cap, props=props)
+ if mirror:
+ print ' Image mirrored'
+ img = np.fliplr(img)
+ for i, color in enumerate(COLOR_BAR_ORDER):
+ tile = its.image.get_image_patch(img, float(i)/num_bars+delta,
+ 0.0, 1.0/num_bars-2*delta, 1.0)
+ color_match.append(np.allclose(its.image.compute_image_means(tile),
+ COLOR_CHECKER[color], atol=CH_TOL))
+ print COLOR_BAR_ORDER
+ print color_match
+ return all(color_match)
+
+
+def check_pattern(cap, props, pattern):
+ """Simple tests for pattern correctness.
+
+ Args:
+ cap: capture element
+ props: capture properties
+ pattern (int): valid number for pattern
+ Returns:
+ boolean
+ """
+
+ # white_level = int(props['android.sensor.info.whiteLevel'])
+ if pattern == 1: # solid color
+ return check_solid_color(cap, props)
+
+ elif pattern == 2: # color bars
+ striped = check_color_bars(cap, props, mirror=False)
+ # check mirrored version in case image rotated from sensor orientation
+ if not striped:
+ striped = check_color_bars(cap, props, mirror=True)
+ return striped
+
+ else:
+ print 'No specific test for TestPattern %d' % pattern
+ return True
+
+
+def test_test_patterns(cam, props, af_fd):
+ """test image sensor test patterns.
+
+ Args:
+ cam: An open device session.
+ props: Properties of cam
+ af_fd: Focus distance
+ """
+
+ avail_patterns = props['android.sensor.availableTestPatternModes']
+ print 'avail_patterns: ', avail_patterns
+ sens_min, _ = props['android.sensor.info.sensitivityRange']
+ exposure = min(props['android.sensor.info.exposureTimeRange'])
+
+ for pattern in PATTERNS:
+ if pattern in avail_patterns:
+ req = its.objects.manual_capture_request(int(sens_min),
+ exposure)
+ req['android.lens.focusDistance'] = af_fd
+ req['android.sensor.testPatternMode'] = pattern
+ fmt = {'format': 'raw'}
+ cap = cam.do_capture(req, fmt)
+ img = its.image.convert_capture_to_rgb_image(cap, props=props)
+
+ # Save pattern
+ its.image.write_image(img, '%s_%d.jpg' % (NAME, pattern), True)
+
+ # Check pattern for correctness
+ assert check_pattern(cap, props, pattern)
+ else:
+ print 'Pattern not in android.sensor.availableTestPatternModes.'
+
+
+def main():
+ """Test pattern generation test.
+
+ Test: capture frames for each valid test pattern and check if
+ generated correctly.
+ android.sensor.testPatternMode
+ 0: OFF
+ 1: SOLID_COLOR
+ 2: COLOR_BARS
+ 3: COLOR_BARS_FADE_TO_GREY
+ 4: PN9
+ """
+
+ print '\nStarting %s' % NAME
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ its.caps.skip_unless(its.caps.raw16(props) and
+ its.caps.manual_sensor(props) and
+ its.caps.per_frame_control(props))
+
+ # For test pattern, use min_fd
+ fd = props['android.lens.info.minimumFocusDistance']
+ test_test_patterns(cam, props, fd)
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf b/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
new file mode 100644
index 0000000..3103cd8
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_3a.py b/apps/CameraITS/tests/scene1/test_3a.py
index 08cd747..2c5dcfe 100644
--- a/apps/CameraITS/tests/scene1/test_3a.py
+++ b/apps/CameraITS/tests/scene1/test_3a.py
@@ -12,8 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.device
import its.caps
+import its.device
+
+import numpy as np
+
def main():
"""Basic test for bring-up of 3A.
@@ -26,14 +29,18 @@
its.caps.skip_unless(its.caps.read_3a(props))
sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
- print "AE: sensitivity %d, exposure %dms" % (sens, exp/1000000)
- print "AWB: gains", gains, "transform", xform
- print "AF: distance", focus
- assert(sens > 0)
- assert(exp > 0)
- assert(len(gains) == 4)
- assert(len(xform) == 9)
- assert(focus >= 0)
+ print 'AE: sensitivity %d, exposure %dms' % (sens, exp/1000000)
+ print 'AWB: gains', gains, 'transform', xform
+ print 'AF: distance', focus
+ assert sens > 0
+ assert exp > 0
+ assert len(gains) == 4
+ for g in gains:
+ assert not np.isnan(g)
+ assert len(xform) == 9
+ for x in xform:
+ assert not np.isnan(x)
+ assert focus >= 0
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene1/test_ae_af.py b/apps/CameraITS/tests/scene1/test_ae_af.py
index 626a475..ed94017 100644
--- a/apps/CameraITS/tests/scene1/test_ae_af.py
+++ b/apps/CameraITS/tests/scene1/test_ae_af.py
@@ -16,7 +16,7 @@
import its.device
import its.target
-import numpy
+import numpy as np
GAIN_LENGTH = 4
TRANSFORM_LENGTH = 9
@@ -39,12 +39,12 @@
for k, v in sorted(SINGLE_A.items()):
print k
try:
- s, e, g, xform, fd = cam.do_3a(get_results=True,
- do_ae=v[0],
- do_af=v[1],
- do_awb=v[2])
+ s, e, gains, xform, fd = cam.do_3a(get_results=True,
+ do_ae=v[0],
+ do_af=v[1],
+ do_awb=v[2])
print ' sensitivity', s, 'exposure', e
- print ' gains', g, 'transform', xform
+ print ' gains', gains, 'transform', xform
print ' fd', fd
print ''
except its.error.Error:
@@ -52,10 +52,14 @@
if k == 'full_3a':
assert s > 0
assert e > 0
- assert len(g) == 4
+ assert len(gains) == 4
+ for g in gains:
+ assert not np.isnan(g)
assert len(xform) == 9
+ for x in xform:
+ assert not np.isnan(x)
assert fd >= 0
- assert numpy.isclose(g[2], GREEN_GAIN, GREEN_GAIN_TOL)
+ assert np.isclose(gains[2], GREEN_GAIN, GREEN_GAIN_TOL)
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
index 2d8c678..94cef80 100644
--- a/apps/CameraITS/tests/scene1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -12,17 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.image
+import os.path
import its.caps
import its.device
+import its.image
import its.objects
import its.target
-import os.path
+
+NAME = os.path.basename(__file__).split('.')[0]
+Y_DELTA = 0.1
+GRADIENT_DELTA = 0.1
+
def main():
- """Test that the android.flash.mode parameter is applied.
- """
- NAME = os.path.basename(__file__).split(".")[0]
+ """Test that the android.flash.mode parameter is applied."""
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
@@ -32,7 +35,8 @@
flash_modes_reported = []
flash_states_reported = []
- g_means = []
+ means = []
+ grads = []
# Manually set the exposure to be a little on the dark side, so that
# it should be obvious whether the flash fired or not, and use a
@@ -45,29 +49,31 @@
match_ar = (largest_yuv['width'], largest_yuv['height'])
fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
- e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+ e, s = its.target.get_target_exposure_combos(cam)['midExposureTime']
e /= 4
req = its.objects.manual_capture_request(s, e, 0.0, True, props)
- for f in [0,1,2]:
- req["android.flash.mode"] = f
+ for f in [0, 1, 2]:
+ req['android.flash.mode'] = f
cap = cam.do_capture(req, fmt)
- flash_modes_reported.append(cap["metadata"]["android.flash.mode"])
- flash_states_reported.append(cap["metadata"]["android.flash.state"])
- img = its.image.convert_capture_to_rgb_image(cap)
- its.image.write_image(img, "%s_mode=%d.jpg" % (NAME, f))
- tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
- rgb = its.image.compute_image_means(tile)
- g_means.append(rgb[1])
+ flash_modes_reported.append(cap['metadata']['android.flash.mode'])
+ flash_states_reported.append(cap['metadata']['android.flash.state'])
+ y, _, _ = its.image.convert_capture_to_planes(cap, props)
+ its.image.write_image(y, '%s_%d.jpg' % (NAME, f))
+ tile = its.image.get_image_patch(y, 0.375, 0.375, 0.25, 0.25)
+ its.image.write_image(tile, '%s_%d_tile.jpg' % (NAME, f))
+ means.append(its.image.compute_image_means(tile)[0])
+ grads.append(its.image.compute_image_max_gradients(tile)[0])
- assert(flash_modes_reported == [0,1,2])
- assert(flash_states_reported[0] not in [3,4])
- assert(flash_states_reported[1] in [3,4])
- assert(flash_states_reported[2] in [3,4])
+ assert flash_modes_reported == [0, 1, 2]
+ assert flash_states_reported[0] not in [3, 4]
+ assert flash_states_reported[1] in [3, 4]
+ assert flash_states_reported[2] in [3, 4]
- print "G brightnesses:", g_means
- assert(g_means[1] > g_means[0])
- assert(g_means[2] > g_means[0])
+ print 'Brightnesses:', means
+ print 'Max gradients: ', grads
+ assert means[1]-means[0] > Y_DELTA or grads[1]-grads[0] > GRADIENT_DELTA
+ assert means[2]-means[0] > Y_DELTA or grads[2]-grads[0] > GRADIENT_DELTA
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
index 343c960..dd7ef21 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
@@ -12,20 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.image
+import math
+import os.path
import its.caps
import its.device
+import its.image
import its.objects
import its.target
-import os.path
-import math
+
+NAME = os.path.basename(__file__).split(".")[0]
+THRESHOLD_MAX_RMS_DIFF = 0.035
+
def main():
- """Test capturing a single frame as both RAW and YUV outputs.
- """
- NAME = os.path.basename(__file__).split(".")[0]
-
- THRESHOLD_MAX_RMS_DIFF = 0.035
+ """Test capturing a single frame as both RAW and YUV outputs."""
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
@@ -38,34 +38,35 @@
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
req = its.objects.manual_capture_request(s, e, 0.0, True, props)
- if 0 in props['android.shading.availableModes']:
- req["android.shading.mode"] = 0
+ mode = req["android.shading.mode"]
+ print "shading mode:", mode
- max_raw_size = \
- its.objects.get_available_output_sizes("raw", props)[0]
- w,h = its.objects.get_available_output_sizes(
+ max_raw_size = its.objects.get_available_output_sizes("raw", props)[0]
+ w, h = its.objects.get_available_output_sizes(
"yuv", props, (1920, 1080), max_raw_size)[0]
- out_surfaces = [{"format":"raw"},
- {"format":"yuv", "width":w, "height":h}]
+ out_surfaces = [{"format": "raw"},
+ {"format": "yuv", "width": w, "height": h}]
cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
img = its.image.convert_capture_to_rgb_image(cap_yuv)
- its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
+ its.image.write_image(img, "%s_shading=%d_yuv.jpg" % (NAME, mode), True)
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
rgb0 = its.image.compute_image_means(tile)
# Raw shots are 1/2 x 1/2 smaller after conversion to RGB, but tile
# cropping is relative.
img = its.image.convert_capture_to_rgb_image(cap_raw, props=props)
- its.image.write_image(img, "%s_raw.jpg" % (NAME), True)
+ its.image.write_image(img, "%s_shading=%d_raw.jpg" % (NAME, mode), True)
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
rgb1 = its.image.compute_image_means(tile)
rms_diff = math.sqrt(
sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
- print "RMS difference:", rms_diff
- assert(rms_diff < THRESHOLD_MAX_RMS_DIFF)
+ msg = "RMS difference: %.4f, spec: %.3f" % (rms_diff,
+ THRESHOLD_MAX_RMS_DIFF)
+ print msg
+ assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
index 6ecdca7..9c0c69b 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
@@ -12,20 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import its.image
+import math
+import os.path
import its.caps
import its.device
+import its.image
import its.objects
import its.target
-import os.path
-import math
+
+NAME = os.path.basename(__file__).split(".")[0]
+THRESHOLD_MAX_RMS_DIFF = 0.035
+
def main():
- """Test capturing a single frame as both RAW10 and YUV outputs.
- """
- NAME = os.path.basename(__file__).split(".")[0]
-
- THRESHOLD_MAX_RMS_DIFF = 0.035
+ """Test capturing a single frame as both RAW10 and YUV outputs."""
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
@@ -38,34 +38,36 @@
e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
req = its.objects.manual_capture_request(s, e, 0.0, True, props)
- if 0 in props['android.shading.availableModes']:
- req["android.shading.mode"] = 0
+ mode = req["android.shading.mode"]
+ print "shading mode:", mode
- max_raw10_size = \
- its.objects.get_available_output_sizes("raw10", props)[0]
- w,h = its.objects.get_available_output_sizes(
+ max_raw10_size = its.objects.get_available_output_sizes("raw10",
+ props)[0]
+ w, h = its.objects.get_available_output_sizes(
"yuv", props, (1920, 1080), max_raw10_size)[0]
- cap_raw, cap_yuv = cam.do_capture(req,
- [{"format":"raw10"},
- {"format":"yuv", "width":w, "height":h}])
+ out_surfaces = [{"format": "raw10"},
+ {"format": "yuv", "width": w, "height": h}]
+ cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
img = its.image.convert_capture_to_rgb_image(cap_yuv)
- its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
+ its.image.write_image(img, "%s_shading=%d_yuv.jpg" % (NAME, mode), True)
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
rgb0 = its.image.compute_image_means(tile)
# Raw shots are 1/2 x 1/2 smaller after conversion to RGB, but tile
# cropping is relative.
img = its.image.convert_capture_to_rgb_image(cap_raw, props=props)
- its.image.write_image(img, "%s_raw.jpg" % (NAME), True)
+ its.image.write_image(img, "%s_shading=%d_raw.jpg" % (NAME, mode), True)
tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
rgb1 = its.image.compute_image_means(tile)
rms_diff = math.sqrt(
sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
- print "RMS difference:", rms_diff
- assert(rms_diff < THRESHOLD_MAX_RMS_DIFF)
+ msg = "RMS difference: %.4f, spec: %.3f" % (rms_diff,
+ THRESHOLD_MAX_RMS_DIFF)
+ print msg
+ assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf b/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf
new file mode 100644
index 0000000..5e7128b
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_faces.py b/apps/CameraITS/tests/scene2/test_faces.py
index 4e30fc1..2acce85 100644
--- a/apps/CameraITS/tests/scene2/test_faces.py
+++ b/apps/CameraITS/tests/scene2/test_faces.py
@@ -60,7 +60,7 @@
# but it should detect at least one face in last frame
if i == NUM_TEST_FRAMES - 1:
img = its.image.convert_capture_to_rgb_image(cap, props=props)
- img = its.image.flip_mirror_img_per_argv(img)
+ img = its.image.rotate_img_per_argv(img)
img_name = "%s_fd_mode_%s.jpg" % (NAME, fd_mode)
its.image.write_image(img, img_name)
if len(faces) == 0:
diff --git a/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf
new file mode 100644
index 0000000..a3e18e2
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene3/test_3a_consistency.py b/apps/CameraITS/tests/scene3/test_3a_consistency.py
index f43b3eb..2dbee4c 100644
--- a/apps/CameraITS/tests/scene3/test_3a_consistency.py
+++ b/apps/CameraITS/tests/scene3/test_3a_consistency.py
@@ -41,14 +41,14 @@
fds = []
for _ in range(NUM_TEST_ITERATIONS):
try:
- s, e, g, xform, fd = cam.do_3a(get_results=True)
+ s, e, gains, xform, fd = cam.do_3a(get_results=True)
print ' sensitivity', s, 'exposure', e
- print ' gains', g, 'transform', xform
+ print ' gains', gains, 'transform', xform
print ' fd', fd
print ''
exps.append(e)
senses.append(s)
- g_gains.append(g[2])
+ g_gains.append(gains[2])
fds.append(fd)
except its.error.Error:
print ' FAIL\n'
@@ -57,6 +57,10 @@
assert np.isclose(np.amax(senses), np.amin(senses), SENS_TOL)
assert np.isclose(np.amax(g_gains), np.amin(g_gains), GGAIN_TOL)
assert np.isclose(np.amax(fds), np.amin(fds), FD_TOL)
+ for g in gains:
+ assert not np.isnan(g)
+ for x in xform:
+ assert not np.isnan(x)
if __name__ == '__main__':
main()
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
new file mode 100644
index 0000000..f0d17ec
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -0,0 +1,135 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import cv2
+
+import its.caps
+import its.cv2image
+import its.device
+import its.image
+import its.objects
+import numpy as np
+
+NAME = os.path.basename(__file__).split('.')[0]
+CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
+ 'test_images', 'ISO12233.png')
+CHART_HEIGHT = 13.5 # cm
+CHART_DISTANCE = 30.0 # cm
+CHART_SCALE_START = 0.65
+CHART_SCALE_STOP = 1.35
+CHART_SCALE_STEP = 0.025
+CHART_ORIENTATIONS = ['nominal', 'flip', 'mirror', 'rotate']
+VGA_WIDTH = 640
+VGA_HEIGHT = 480
+(X_CROP, Y_CROP) = (0.5, 0.5) # crop center area of ISO12233 chart
+
+
+def test_flip_mirror(cam, props, fmt, chart):
+ """Return if image is flipped or mirrored.
+
+ Args:
+ cam (class): An open device session
+ props (class): Properties of cam
+ fmt (dict): Capture format
+ chart (class): Object with chart properties
+
+ Returns:
+ boolean: True if flipped, False if not
+ """
+
+ # determine if in debug mode
+ debug = its.caps.debug_mode()
+
+ # get a local copy of the chart template
+ template = cv2.imread(CHART_FILE, cv2.IMREAD_ANYDEPTH)
+
+ # take img, crop chart, scale and prep for cv2 template match
+ s, e, _, _, fd = cam.do_3a(get_results=True)
+ req = its.objects.manual_capture_request(s, e, fd)
+ cap = cam.do_capture(req, fmt)
+ y, _, _ = its.image.convert_capture_to_planes(cap, props)
+ y = its.image.rotate_img_per_argv(y)
+ patch = its.image.get_image_patch(y, chart.xnorm, chart.ynorm,
+ chart.wnorm, chart.hnorm)
+ patch = 255 * its.cv2image.gray_scale_img(patch)
+ patch = its.cv2image.scale_img(patch.astype(np.uint8), chart.scale)
+
+ # sanity check on image
+ assert np.max(patch)-np.min(patch) > 255/8
+
+ # save full images if in debug
+ if debug:
+ its.image.write_image(template[:, :, np.newaxis]/255.0,
+ '%s_template.jpg' % NAME)
+
+ # save patch
+ its.image.write_image(patch[:, :, np.newaxis]/255.0,
+ '%s_scene_patch.jpg' % NAME)
+
+ # crop center areas and strip off any extra rows/columns
+ template = its.image.get_image_patch(template, (1-X_CROP)/2, (1-Y_CROP)/2,
+ X_CROP, Y_CROP)
+ patch = its.image.get_image_patch(patch, (1-X_CROP)/2,
+ (1-Y_CROP)/2, X_CROP, Y_CROP)
+ patch = patch[0:min(patch.shape[0], template.shape[0]),
+ 0:min(patch.shape[1], template.shape[1])]
+ comp_chart = patch
+
+ # determine optimum orientation
+ opts = []
+ for orientation in CHART_ORIENTATIONS:
+ if orientation == 'flip':
+ comp_chart = np.flipud(patch)
+ elif orientation == 'mirror':
+ comp_chart = np.fliplr(patch)
+ elif orientation == 'rotate':
+ comp_chart = np.flipud(np.fliplr(patch))
+ correlation = cv2.matchTemplate(comp_chart, template, cv2.TM_CCOEFF)
+ _, opt_val, _, _ = cv2.minMaxLoc(correlation)
+ if debug:
+ cv2.imwrite('%s_%s.jpg' % (NAME, orientation), comp_chart)
+ print ' %s correlation value: %d' % (orientation, opt_val)
+ opts.append(opt_val)
+
+ # determine if 'nominal' or 'rotated' is best orientation
+ assert_flag = (opts[0] == max(opts) or opts[3] == max(opts))
+ assert assert_flag, ('Optimum orientation is %s' %
+ CHART_ORIENTATIONS[np.argmax(opts)])
+ # print warning if rotated
+ if opts[3] == max(opts):
+ print 'Image is rotated 180 degrees. Try "rotate" flag.'
+
+
+def main():
+ """Test if image is properly oriented."""
+
+ print '\nStarting test_flip_mirror.py'
+
+ # initialize chart class and locate chart in scene
+ chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+ CHART_SCALE_START, CHART_SCALE_STOP,
+ CHART_SCALE_STEP)
+
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ its.caps.skip_unless(its.caps.read_3a(props))
+ fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+
+ # test that image is not flipped, mirrored, or rotated
+ test_flip_mirror(cam, props, fmt, chart)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index cd563be..aaa8484 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -37,19 +37,20 @@
CHART_SCALE_STEP = 0.025
-def test_lens_movement_reporting(cam, props, fmt, sensitivity, exp, af_fd):
+def test_lens_movement_reporting(cam, props, fmt, gain, exp, af_fd, chart):
"""Return fd, sharpness, lens state of the output images.
Args:
cam: An open device session.
props: Properties of cam
fmt: dict; capture format
- sensitivity: Sensitivity for the 3A request as defined in
+ gain: Sensitivity for the 3A request as defined in
android.sensor.sensitivity
exp: Exposure time for the 3A request as defined in
android.sensor.exposureTime
af_fd: Focus distance for the 3A request as defined in
android.lens.focusDistance
+ chart: Object that contains chart information
Returns:
Object containing reported sharpness of the output image, keyed by
@@ -57,15 +58,6 @@
'sharpness'
"""
- # initialize chart class
- chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
- CHART_SCALE_START, CHART_SCALE_STOP,
- CHART_SCALE_STEP)
-
- # find chart location
- xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,
- exp, af_fd)
-
# initialize variables and take data sets
data_set = {}
white_level = int(props['android.sensor.info.whiteLevel'])
@@ -74,7 +66,7 @@
fds = sorted(fds * NUM_IMGS)
reqs = []
for i, fd in enumerate(fds):
- reqs.append(its.objects.manual_capture_request(sensitivity, exp))
+ reqs.append(its.objects.manual_capture_request(gain, exp))
reqs[i]['android.lens.focusDistance'] = fd
caps = cam.do_capture(reqs, fmt)
for i, cap in enumerate(caps):
@@ -92,12 +84,12 @@
print ' current lens location (diopters): %.3f' % data['loc']
print ' lens moving %r' % data['lens_moving']
y, _, _ = its.image.convert_capture_to_planes(cap, props)
- y = its.image.flip_mirror_img_per_argv(y)
- chart = its.image.normalize_img(its.image.get_image_patch(y,
- xnorm, ynorm,
- wnorm, hnorm))
- its.image.write_image(chart, '%s_i=%d_chart.jpg' % (NAME, i))
- data['sharpness'] = white_level*its.image.compute_image_sharpness(chart)
+ y = its.image.rotate_img_per_argv(y)
+ chart.img = its.image.normalize_img(its.image.get_image_patch(
+ y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+ its.image.write_image(chart.img, '%s_i=%d_chart.jpg' % (NAME, i))
+ data['sharpness'] = white_level*its.image.compute_image_sharpness(
+ chart.img)
print 'Chart sharpness: %.1f\n' % data['sharpness']
data_set[i] = data
return data_set
@@ -110,10 +102,16 @@
"""
print '\nStarting test_lens_movement_reporting.py'
+ # initialize chart class
+ chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+ CHART_SCALE_START, CHART_SCALE_STOP,
+ CHART_SCALE_STEP)
+
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
its.caps.skip_unless(not its.caps.fixed_focus(props))
- its.caps.skip_unless(its.caps.lens_approx_calibrated(props))
+ its.caps.skip_unless(its.caps.read_3a(props) and
+ its.caps.lens_approx_calibrated(props))
min_fd = props['android.lens.info.minimumFocusDistance']
fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
@@ -121,7 +119,7 @@
s, e, _, _, fd = cam.do_3a(get_results=True)
# Get sharpness for each focal distance
- d = test_lens_movement_reporting(cam, props, fmt, s, e, fd)
+ d = test_lens_movement_reporting(cam, props, fmt, s, e, fd, chart)
for k in sorted(d):
print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
'sharpness: %.1f \tlens_moving: %r \t'
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index f850e3d..feb28a8 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -38,7 +38,7 @@
CHART_SCALE_STEP = 0.025
-def test_lens_position(cam, props, fmt, sensitivity, exp, af_fd):
+def test_lens_position(cam, props, fmt, sensitivity, exp, chart):
"""Return fd, sharpness, lens state of the output images.
Args:
@@ -49,8 +49,7 @@
android.sensor.sensitivity
exp: Exposure time for the 3A request as defined in
android.sensor.exposureTime
- af_fd: Focus distance for the 3A request as defined in
- android.lens.focusDistance
+ chart: Object with chart properties
Returns:
Dictionary of results for different focal distance captures
@@ -58,15 +57,6 @@
d_static, d_moving
"""
- # initialize chart class
- chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
- CHART_SCALE_START, CHART_SCALE_STOP,
- CHART_SCALE_STEP)
-
- # find chart location
- xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,
- exp, af_fd)
-
# initialize variables and take data sets
data_static = {}
data_moving = {}
@@ -89,11 +79,11 @@
print ' focus distance (diopters): %.3f' % data['fd']
print ' current lens location (diopters): %.3f' % data['loc']
y, _, _ = its.image.convert_capture_to_planes(cap, props)
- chart = its.image.normalize_img(its.image.get_image_patch(y,
- xnorm, ynorm,
- wnorm, hnorm))
- its.image.write_image(chart, '%s_stat_i=%d_chart.jpg' % (NAME, i))
- data['sharpness'] = white_level*its.image.compute_image_sharpness(chart)
+ chart.img = its.image.normalize_img(its.image.get_image_patch(
+ y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+ its.image.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (NAME, i))
+ data['sharpness'] = white_level*its.image.compute_image_sharpness(
+ chart.img)
print 'Chart sharpness: %.1f\n' % data['sharpness']
data_static[i] = data
# take moving data set
@@ -115,12 +105,12 @@
print ' focus distance (diopters): %.3f' % data['fd']
print ' current lens location (diopters): %.3f' % data['loc']
y, _, _ = its.image.convert_capture_to_planes(cap, props)
- y = its.image.flip_mirror_img_per_argv(y)
- chart = its.image.normalize_img(its.image.get_image_patch(y,
- xnorm, ynorm,
- wnorm, hnorm))
- its.image.write_image(chart, '%s_move_i=%d_chart.jpg' % (NAME, i))
- data['sharpness'] = white_level*its.image.compute_image_sharpness(chart)
+ y = its.image.rotate_img_per_argv(y)
+ chart.img = its.image.normalize_img(its.image.get_image_patch(
+ y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+ its.image.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (NAME, i))
+ data['sharpness'] = white_level*its.image.compute_image_sharpness(
+ chart.img)
print 'Chart sharpness: %.1f\n' % data['sharpness']
data_moving[i] = data
return data_static, data_moving
@@ -128,19 +118,24 @@
def main():
"""Test if focus position is properly reported for moving lenses."""
-
print '\nStarting test_lens_position.py'
+ # initialize chart class
+ chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+ CHART_SCALE_START, CHART_SCALE_STOP,
+ CHART_SCALE_STEP)
+
with its.device.ItsSession() as cam:
props = cam.get_camera_properties()
its.caps.skip_unless(not its.caps.fixed_focus(props))
- its.caps.skip_unless(its.caps.lens_calibrated(props))
+ its.caps.skip_unless(its.caps.read_3a(props) and
+ its.caps.lens_calibrated(props))
fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
- # Get proper sensitivity, exposure time, and focus distance with 3A.
- s, e, _, _, fd = cam.do_3a(get_results=True)
+ # Get proper sensitivity and exposure time with 3A
+ s, e, _, _, _ = cam.do_3a(get_results=True)
# Get sharpness for each focal distance
- d_stat, d_move = test_lens_position(cam, props, fmt, s, e, fd)
+ d_stat, d_move = test_lens_position(cam, props, fmt, s, e, chart)
print 'Lens stationary'
for k in sorted(d_stat):
print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
diff --git a/apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf
new file mode 100644
index 0000000..7fb1e42
--- /dev/null
+++ b/apps/CameraITS/tests/scene4/scene4_0.67_scaled.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
index e8a5b81..faacaf9 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -116,7 +116,7 @@
# Split by comma and convert each dimension to int.
[w, h] = map(int, s[9:].split(","))
elif s[:12] == "test_length=" and len(s) > 12:
- test_length = int(s[12:])
+ test_length = float(s[12:])
# 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
@@ -345,7 +345,7 @@
if num_features < MIN_FEATURE_PTS:
print "Not enough feature points in frame", i
print "Need at least %d features, got %d" % (
- MIN_FEATURE_PTS, num_features)
+ MIN_FEATURE_PTS, num_features)
assert 0
else:
print "Number of features in frame %d is %d" % (i, num_features)
@@ -447,13 +447,12 @@
fmt = {"format": "yuv", "width": w, "height": h}
s, e, _, _, _ = cam.do_3a(get_results=True, do_af=False)
req = its.objects.manual_capture_request(s, e)
- fps = 30
req["android.lens.focusDistance"] = 1 / (CHART_DISTANCE * CM_TO_M)
req["android.control.aeTargetFpsRange"] = [fps, fps]
req["android.sensor.frameDuration"] = int(1000.0/fps * MSEC_TO_NSEC)
- print "Capturing %dx%d with sens. %d, exp. time %.1fms" % (
- w, h, s, e*NSEC_TO_MSEC)
- caps = cam.do_capture([req]*fps*test_length, fmt)
+ print "Capturing %dx%d with sens. %d, exp. time %.1fms at %dfps" % (
+ w, h, s, e*NSEC_TO_MSEC, fps)
+ caps = cam.do_capture([req]*int(fps*test_length), fmt)
# Get the gyro events.
print "Reading out sensor events"
diff --git a/apps/CameraITS/tools/load_scene.py b/apps/CameraITS/tools/load_scene.py
index 4e245f4..b46a311 100644
--- a/apps/CameraITS/tools/load_scene.py
+++ b/apps/CameraITS/tools/load_scene.py
@@ -21,13 +21,16 @@
def main():
"""Load charts on device and display."""
- camera_id = -1
scene = None
for s in sys.argv[1:]:
if s[:6] == 'scene=' and len(s) > 6:
scene = s[6:]
elif s[:7] == 'screen=' and len(s) > 7:
screen_id = s[7:]
+ elif s[:5] == 'dist=' and len(s) > 5:
+ chart_distance = float(re.sub('cm', '', s[5:]))
+ elif s[:4] == 'fov=' and len(s) > 4:
+ camera_fov = float(s[4:])
cmd = ('adb -s %s shell am force-stop com.google.android.apps.docs' %
screen_id)
@@ -43,8 +46,13 @@
remote_scene_file = '/sdcard/Download/%s.pdf' % scene
local_scene_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests',
- scene, scene+'.pdf')
- print 'Loading %s on %s' % (remote_scene_file, screen_id)
+ scene)
+ if chart_distance == 20 and camera_fov < 90:
+ local_scene_file = os.path.join(local_scene_file,
+ scene+'_0.67_scaled.pdf')
+ else:
+ local_scene_file = os.path.join(local_scene_file, scene+'.pdf')
+ print 'Loading %s on %s' % (local_scene_file, screen_id)
cmd = 'adb -s %s push %s /mnt%s' % (screen_id, local_scene_file,
remote_scene_file)
subprocess.Popen(cmd.split())
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 47f7296..17d49ec 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -13,21 +13,50 @@
# limitations under the License.
import copy
+import math
import os
import os.path
-import tempfile
+import re
import subprocess
-import time
import sys
+import tempfile
+import time
import its.caps
+import its.cv2image
import its.device
from its.device import ItsSession
+import its.image
CHART_DELAY = 1 # seconds
+CHART_DISTANCE = 30.0 # cm
+CHART_HEIGHT = 13.5 # cm
+CHART_SCALE_START = 0.65
+CHART_SCALE_STOP = 1.35
+CHART_SCALE_STEP = 0.025
FACING_EXTERNAL = 2
NUM_TRYS = 2
+SCENE3_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its',
+ 'test_images', 'ISO12233.png')
SKIP_RET_CODE = 101 # note this must be same as tests/scene*/test_*
+VGA_HEIGHT = 480
+VGA_WIDTH = 640
+
+
+def calc_camera_fov():
+ """Determine the camera field of view from internal params."""
+ with ItsSession() as cam:
+ props = cam.get_camera_properties()
+ try:
+ focal_l = props['android.lens.info.availableFocalLengths'][0]
+ sensor_size = props['android.sensor.info.physicalSize']
+ diag = math.sqrt(sensor_size['height'] ** 2 +
+ sensor_size['width'] ** 2)
+ fov = str(round(2 * math.degrees(math.atan(diag / (2 * focal_l))), 2))
+ except ValueError:
+ fov = str(0)
+ print 'Calculated FoV: %s' % fov
+ return fov
def evaluate_socket_failure(err_file_path):
@@ -79,6 +108,7 @@
tmp_dir: location of temp directory for output files
skip_scene_validation: force skip scene validation. Used when test scene
is setup up front and don't require tester validation.
+ dist: [Experimental] chart distance in cm.
"""
# Not yet mandated tests
@@ -100,6 +130,7 @@
],
"scene3": [
"test_3a_consistency",
+ "test_flip_mirror",
"test_lens_movement_reporting",
"test_lens_position"
],
@@ -139,6 +170,10 @@
rot_rig_id = None
tmp_dir = None
skip_scene_validation = False
+ chart_distance = CHART_DISTANCE
+ chart_height = CHART_HEIGHT
+ approx_equal = lambda a, b, t: abs(a - b) < t
+
for s in sys.argv[1:]:
if s[:7] == "camera=" and len(s) > 7:
camera_ids = s[7:].split(',')
@@ -155,6 +190,8 @@
tmp_dir = s[8:]
elif s == 'skip_scene_validation':
skip_scene_validation = True
+ elif s[:5] == 'dist=' and len(s) > 5:
+ chart_distance = float(re.sub('cm', '', s[5:]))
auto_scene_switch = chart_host_id is not None
merge_result_switch = result_device_id is not None
@@ -183,7 +220,7 @@
break
if not valid_scenes:
- print "Unknown scene specifiied:", s
+ print 'Unknown scene specified:', s
assert False
scenes = temp_scenes
@@ -245,6 +282,7 @@
assert wake_code == 0
for camera_id in camera_ids:
+ camera_fov = calc_camera_fov()
# Loop capturing images until user confirm test scene is correct
camera_id_arg = "camera=" + camera_id
print "Preparing to run ITS on camera", camera_id
@@ -280,9 +318,11 @@
if (not merge_result_switch or
(merge_result_switch and camera_ids[0] == '0')):
scene_arg = 'scene=' + scene
+ chart_dist_arg = 'dist= ' + str(chart_distance)
+ fov_arg = 'fov=' + camera_fov
cmd = ['python',
os.path.join(os.getcwd(), 'tools/load_scene.py'),
- scene_arg, screen_id_arg]
+ scene_arg, chart_dist_arg, fov_arg, screen_id_arg]
else:
time.sleep(CHART_DELAY)
else:
@@ -299,6 +339,18 @@
valid_scene_code = subprocess.call(cmd, cwd=topdir)
assert valid_scene_code == 0
print "Start running ITS on camera %s, %s" % (camera_id, scene)
+ # Extract chart from scene for scene3 once up front
+ chart_loc_arg = ''
+ if scene == 'scene3':
+ if float(camera_fov) < 90 and approx_equal(chart_distance, 20,
+ 1E-6):
+ chart_height *= 0.67
+ chart = its.cv2image.Chart(SCENE3_FILE, chart_height,
+ chart_distance, CHART_SCALE_START,
+ CHART_SCALE_STOP, CHART_SCALE_STEP)
+ chart_loc_arg = 'chart_loc=%.2f,%.2f,%.2f,%.2f,%.3f' % (
+ chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm,
+ chart.scale)
# Run each test, capturing stdout and stderr.
for (testname, testpath) in tests:
if auto_scene_switch:
@@ -330,7 +382,7 @@
test_code = skip_code
if skip_code is not SKIP_RET_CODE:
cmd = ['python', os.path.join(os.getcwd(), testpath)]
- cmd += sys.argv[1:] + [camera_id_arg]
+ cmd += sys.argv[1:] + [camera_id_arg] + [chart_loc_arg]
with open(outpath, 'w') as fout, open(errpath, 'w') as ferr:
test_code = subprocess.call(
cmd, stderr=ferr, stdout=fout, cwd=outdir)
diff --git a/apps/CameraITS/tools/run_sensor_fusion_box.py b/apps/CameraITS/tools/run_sensor_fusion_box.py
index ec16b3d..41f1180 100644
--- a/apps/CameraITS/tools/run_sensor_fusion_box.py
+++ b/apps/CameraITS/tools/run_sensor_fusion_box.py
@@ -82,10 +82,6 @@
elif s[:8] == 'tmp_dir=' and len(s) > 8:
tmp_dir = s[8:]
- if camera_id not in ['0', '1']:
- print 'Need to specify camera 0 or 1'
- sys.exit()
-
# Make output directories to hold the generated files.
tmpdir = tempfile.mkdtemp(dir=tmp_dir)
print 'Saving output files to:', tmpdir, '\n'
@@ -94,6 +90,12 @@
device_id_arg = 'device=' + device_id
print 'Testing device ' + device_id
+ # ensure camera_id is valid
+ avail_camera_ids = find_avail_camera_ids(device_id_arg, tmpdir)
+ if camera_id not in avail_camera_ids:
+ print 'Need to specify valid camera_id in ', avail_camera_ids
+ sys.exit()
+
camera_id_arg = 'camera=' + camera_id
if rotator_ids:
rotator_id_arg = 'rotator=' + rotator_ids
@@ -104,6 +106,7 @@
fps_arg = 'fps=' + fps
test_length_arg = 'test_length=' + test_length
+ print 'Capturing at %sfps' % fps
os.mkdir(os.path.join(tmpdir, camera_id))
@@ -217,6 +220,28 @@
return line
return None
+def find_avail_camera_ids(device_id_arg, tmpdir):
+ """Find the available camera IDs.
+
+ Args:
+ devices_id_arg(str): device=###
+ tmpdir(str): generated tmp dir for run
+ Returns:
+ list of available cameras
+ """
+ avail_camera_ids = []
+ camera_ids_path = os.path.join(tmpdir, 'camera_ids.txt')
+ out_arg = 'out=' + camera_ids_path
+ cmd = ['python',
+ os.path.join(os.getcwd(), 'tools/get_camera_ids.py'), out_arg,
+ device_id_arg]
+ cam_code = subprocess.call(cmd, cwd=tmpdir)
+ assert cam_code == 0
+ with open(camera_ids_path, "r") as f:
+ for line in f:
+ avail_camera_ids.append(line.replace('\n', ''))
+ return avail_camera_ids
+
if __name__ == '__main__':
main()
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 9fcbdb1..10c33ba 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -38,9 +38,14 @@
mockito-target-minus-junit4 \
mockwebserver \
compatibility-device-util \
- platform-test-annotations
+ platform-test-annotations \
+ cts-security-test-support-library
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES += telephony-common
+LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
+LOCAL_JAVA_LIBRARIES += android.test.mock.stubs
+LOCAL_JAVA_LIBRARIES += bouncycastle
LOCAL_PACKAGE_NAME := CtsVerifier
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 514056a..5f7ee79 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -67,6 +67,12 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.READ_SMS"/>
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.SEND_SMS" />
+
<!-- Needed by UsbTest tapjacking -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
@@ -82,6 +88,11 @@
android:largeHeap="true"
android:theme="@android:style/Theme.DeviceDefault">
+ <provider android:name="android.location.cts.MmsPduProvider"
+ android:authorities="emergencycallverifier"
+ android:grantUriPermissions="true" />
+ <uses-library android:name="android.test.runner" />
+
<meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
<meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
@@ -167,8 +178,13 @@
android:value="android.software.companion_device_setup" />
</activity>
- <!-- A generic activity for intent based tests -->
- <activity android:name=".IntentDrivenTestActivity"/>
+ <!-- A generic activity for intent based tests.
+ stateNotNeeded is defined ot prevent IntentDrivenTestActivity from being killed when
+ switching users. IntentDrivenTestActivity does not implement onSaveInstanceState() so it is
+ fine to ignore onSaveInstanceState() not being called.
+ -->
+ <activity android:name=".IntentDrivenTestActivity"
+ android:stateNotNeeded="true"/>
<activity android:name=".admin.DeviceAdminKeyguardDisabledFeaturesActivity"
android:label="@string/da_kg_disabled_features_test"
@@ -1099,6 +1115,43 @@
<meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
</activity>
+ <activity android:name=".location.EmergencyCallWifiTestsActivity"
+ android:label="@string/location_emergency_call_wifi_test"
+ android:screenOrientation="locked">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.cts.intent.category.MANUAL_TEST"/>
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_hardware"/>
+ <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+ </activity>
+
+ <activity android:name=".location.EmergencyCallMessageTestsActivity"
+ android:label="@string/location_emergency_call_message_test"
+ android:screenOrientation="locked">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.cts.intent.category.MANUAL_TEST"/>
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_hardware"/>
+ <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.telephony"/>
+ </activity>
+
+ <activity android:name=".location.EmergencyCallGNSSTestsActivity"
+ android:label="@string/location_emergency_call_gps_test"
+ android:screenOrientation="locked">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.cts.intent.category.MANUAL_TEST"/>
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_hardware"/>
+ <meta-data android:name="test_required_features" android:value="android.hardware.location.gps" />
+ <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
+ </activity>
+
<activity android:name=".location.GnssMeasurementWhenNoLocationTestsActivity"
android:label="@string/location_gnss_measure_no_location_test"
android:screenOrientation="locked">
@@ -1829,6 +1882,13 @@
<meta-data android:name="test_category" android:value="@string/test_category_notifications" />
</activity>
+ <receiver android:name=".notifications.BlockChangeReceiver">
+ <intent-filter>
+ <action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"/>
+ <action android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"/>
+ </intent-filter>
+ </receiver>
+
<activity android:name=".notifications.ConditionProviderVerifierActivity"
android:label="@string/cp_test">
<intent-filter>
@@ -2266,6 +2326,16 @@
<meta-data android:name="test_required_features" android:value="android.software.device_admin" />
</activity>
+ <activity android:name=".managedprovisioning.ManagedUserPositiveTestActivity"
+ android:label="@string/managed_user_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_AFFILIATED_PROFILE_OWNER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".managedprovisioning.DeviceOwnerRequestingBugreportTestActivity"
android:label="@string/device_owner_requesting_bugreport_tests">
<intent-filter>
@@ -2278,14 +2348,6 @@
<meta-data android:name="test_required_features" android:value="android.software.device_admin" />
</activity>
- <activity android:name=".managedprovisioning.DeviceOwnerPositiveTestActivity$CommandReceiver"
- android:exported="false"
- android:theme="@android:style/Theme.NoDisplay"
- android:noHistory="true"
- android:autoRemoveFromRecents="true"
- android:stateNotNeeded="true">
- </activity>
-
<activity android:name=".managedprovisioning.KeyguardDisabledFeaturesActivity"
android:label="@string/provisioning_byod_keyguard_disabled_features">
</activity>
@@ -2294,6 +2356,14 @@
android:label="@string/provisioning_byod_disallow_apps_control">
</activity>
+ <activity android:name=".managedprovisioning.LockTaskUiTestActivity"
+ android:label="@string/device_owner_lock_task_ui_test">
+ <intent-filter>
+ <action android:name="com.android.cts.verifier.managedprovisioning.action.STOP_LOCK_TASK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".managedprovisioning.WifiLockdownTestActivity"
android:label="@string/device_owner_wifi_lockdown_test">
</activity>
@@ -2321,6 +2391,14 @@
</intent-filter>
</activity>
+ <activity android:name=".managedprovisioning.KeyChainTestActivity"
+ android:label="@string/provisioning_byod_keychain">
+ <intent-filter>
+ <action android:name="com.android.cts.verifier.managedprovisioning.KEYCHAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".managedprovisioning.PermissionLockdownTestActivity"
android:label="@string/device_profile_owner_permission_lockdown_test">
<intent-filter>
diff --git a/apps/CtsVerifier/res/layout-small/positive_managed_user.xml b/apps/CtsVerifier/res/layout-small/positive_managed_user.xml
new file mode 100644
index 0000000..84fa363
--- /dev/null
+++ b/apps/CtsVerifier/res/layout-small/positive_managed_user.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/RootLayoutPadding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/positive_managed_user_instructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/managed_user_positive_tests_instructions"
+ android:textSize="16dip" />
+
+ <ListView
+ android:id="@+id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="800dip" />
+
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/device_owner_lock_task_ui.xml b/apps/CtsVerifier/res/layout/device_owner_lock_task_ui.xml
new file mode 100644
index 0000000..00dee1a
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/device_owner_lock_task_ui.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/RootLayoutPadding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/device_owner_lock_task_ui_instructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/device_owner_lock_task_ui_test_info"
+ android:textSize="18dip" />
+
+ <Button
+ android:id="@+id/start_lock_task_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/start_lock_task_button_label" />
+
+ <ListView
+ android:id="@+id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/emergency_call_confirm_dialog.xml b/apps/CtsVerifier/res/layout/emergency_call_confirm_dialog.xml
new file mode 100644
index 0000000..fae5167
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/emergency_call_confirm_dialog.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ style="@style/RootLayoutPadding">
+
+ <TextView android:id="@+id/info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="18sp"
+ android:padding="5dp"
+ android:text="@string/emergency_call_confirm_info"/>
+ <EditText
+ android:id="@+id/emergency_number"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:hint="@string/emergency_call_emergency_number_hint_text"
+ android:inputType="phone"
+ android:text="@string/emergency_call_emergency_number_text"/>
+ <Button
+ android:id="@+id/dial_button"
+ android:layout_width="200px"
+ android:layout_height="wrap_content"
+ android:text="@string/emergency_call_dial_text"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginRight="5dp"/>
+ </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/emergency_call_msg_test_confirm_dialog.xml b/apps/CtsVerifier/res/layout/emergency_call_msg_test_confirm_dialog.xml
new file mode 100644
index 0000000..071e2bf
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/emergency_call_msg_test_confirm_dialog.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ style="@style/RootLayoutPadding">
+
+ <TextView android:id="@+id/info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="18sp"
+ android:padding="5dp"
+ android:text="@string/emergency_call_confirm_info"/>
+ <EditText
+ android:id="@+id/emergency_number"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:hint="@string/emergency_call_emergency_number_hint_text"
+ android:inputType="phone"
+ android:text="@string/emergency_call_emergency_number_text"/>
+ <EditText
+ android:id="@+id/local_phone_number"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:hint="@string/emergency_call_current_number_hint_text"
+ android:inputType="phone"/>
+ <Button
+ android:id="@+id/dial_button"
+ android:layout_width="200px"
+ android:layout_height="wrap_content"
+ android:text="@string/emergency_call_dial_text"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginRight="5dp"/>
+ </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/keychain_test.xml b/apps/CtsVerifier/res/layout/keychain_test.xml
new file mode 100644
index 0000000..09708c7
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/keychain_test.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="320dip"
+ android:layout_weight="2">
+ <TextView
+ android:id="@+id/provisioning_byod_keychain_instructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dip"
+ android:text="@string/provisioning_byod_keychain_info_start"
+ android:textSize="18dip" />
+ </ScrollView>
+
+ <TextView
+ android:id="@+id/provisioning_byod_keychain_test_log"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dip"
+ android:textSize="18dip" />
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/prepare_test_button"
+ android:layout_width="204dip"
+ android:layout_height="wrap_content"
+ android:text="@string/provisioning_byod_keyguard_disabled_features_prepare_button"/>
+
+ <Button
+ android:id="@+id/run_test_button"
+ android:layout_width="204dip"
+ android:layout_height="wrap_content"
+ android:text="@string/go_button_text"/>
+
+ </LinearLayout>
+
+ <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/positive_managed_user.xml b/apps/CtsVerifier/res/layout/positive_managed_user.xml
new file mode 100644
index 0000000..4afdf63
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/positive_managed_user.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/RootLayoutPadding"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/positive_managed_user_instructions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/managed_user_positive_tests_instructions"
+ android:textSize="18dip" />
+
+ <ListView
+ android:id="@+id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <include layout="@layout/pass_fail_buttons" />
+ </LinearLayout>
+ </ScrollView>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 2ca58ab..14706dd 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -566,10 +566,32 @@
(for example, stationary on a windowsill. If needed, try again, outside, also with the
device stationary, and with at least some view of the sky.) and then press Next to run
the automated tests.</string>
+ <string name="location_emergency_call_test_info">This test verifies whether basic features
+ (wifi, sms, gps) works correctly during the emergency call. Make sure the device is using
+ a special white listed sim card. The wifi and GPS should be on and have internet connection.
+ </string>
+ <string name="emergency_call_confirm_info">This test will dial 911! Please make sure to use a
+ whitelisted sim card to run this test!
+ </string>
+ <string name="emergency_call_emergency_number_hint_text">
+ Emergency Number:
+ </string>
+ <string name="emergency_call_current_number_hint_text">
+ Current Number:
+ </string>
+ <string name="emergency_call_dial_text">
+ Dial 911
+ </string>
+ <string name="emergency_call_emergency_number_text">
+ 911
+ </string>
<string name="location_gnss_test_retry_info">If this test fails, please make sure the device
has line of sight to GNSS satellites (for example, stationary on a windowsill. If needed,
try again, outside, also with the device stationary, with as much view of the sky as
possible.) </string>
+ <string name="location_emergency_call_gps_test">Emergency Call GPS Test</string>
+ <string name="location_emergency_call_message_test">Emergency Call Message Test</string>
+ <string name="location_emergency_call_wifi_test">Emergency Call Wifi Test</string>
<!-- Strings for net.ConnectivityScreenOffTestActivity -->
<string name="network_screen_off_test">Network Connectivity Screen Off Test</string>
@@ -1523,8 +1545,11 @@
<string name="attention_phone_order">Check that ranker respects telephone URIs for contacts.</string>
<string name="attention_interruption_order">Check that ranker temporarily boosts interruptions.
This test takes 30 seconds to complete.</string>
- <string name="attention_none_are_filtered">Check that \"All\" mode doesn\'t filter any notifications.</string>
- <string name="attention_some_are_filtered">Check that \"Priority\" mode doesn\'t filter priority notifications.</string>
+ <string name="attention_none_are_filtered_messages">Check that \"All\" mode doesn\'t filter any notifications (messages).</string>
+ <string name="attention_none_are_filtered_diff_categories">Check that \"All\" mode doesn\'t filter any notifications (event, reminder, alarm).</string>
+ <string name="attention_some_are_filtered_messages">Check that \"Priority\" mode doesn\'t filter priority notifications (messages from starred contacts).</string>
+ <string name="attention_some_are_filtered_alarms">Check that \"Priority\" mode doesn\'t filter priority notifications (alarms).</string>
+ <string name="attention_some_are_filtered_media_system_other">Check that \"Priority\" mode doesn\'t filter priority notifications (media, system, other).</string>
<string name="attention_all_are_filtered">Check that \"None\" mode filters all notifications.</string>
<string name="nls_test">Notification Listener Test</string>
<string name="nas_test">Notification Assistant Test</string>
@@ -1562,6 +1587,10 @@
under Apps > Gear Icon > Default > Notification Assistant and return here.</string>
<string name="nls_enable_service">Please enable \"Notification Listener for CTS Verifier\"
under Apps > Gear Icon > Special Access > Notification Access and return here.</string>
+ <string name="nls_block_channel">Please block the linked notification channel and return here.</string>
+ <string name="nls_block_group">Please block the linked notification channel group and return here.</string>
+ <string name="nls_cannot_enable_service">Please make sure you cannot enable
+ \"Notification Listener for CTS Verifier\" and return here.</string>
<string name="nls_disable_service">Please disable \"Notification Listener for CTS Verifier\"
under Apps > Gear Icon > Special Access > Notification Access and return here.</string>
<string name="nls_start_settings">Launch Settings</string>
@@ -1578,6 +1607,7 @@
<string name="nas_snooze_context">Check that the Assistant can snooze a notification until a given context.</string>
<string name="nls_clear_one">Check that service can clear a notification.</string>
<string name="nls_clear_one_reason">Check that service can clear a notification and receive the correct reason for dismissal.</string>
+ <string name="nls_clear_one_stats">Check that service does not receive notification stats.</string>
<string name="nls_clear_all">Check that service can clear all notifications.</string>
<string name="nls_service_stopped">Service should stop once disabled.</string>
<string name="nls_note_missed">Check that notification was not received.</string>
@@ -1591,10 +1621,11 @@
<string name="nas_note_missed_enqueued">Check that notification was not enqueued.</string>
<string name="cp_test">Condition Provider test</string>
<string name="cp_service_name">Condition Provider for CTS Verifier</string>
- <string name="cp_info">This test checks that a ConditionProviderService can be enabled
+ <string name="cp_info">This test checks that on non-low ram a ConditionProviderService can be enabled
and disabled, and that once enabled the service is able to create, query, edit, and delete
- automatic zen rules.
+ automatic zen rules. On low ram devices condition providers should not be bound.
</string>
+ <string name="cp_cannot_enable_service">Please make sure you cannot enable \"CTS Verifier\" under Do Not Disturb access and return here.</string>
<string name="cp_enable_service">Please enable \"CTS Verifier\" under Do Not Disturb access and return here.</string>
<string name="cp_disable_service">Please disable \"CTS Verifier\" under Do Not Disturb access and return here.</string>
<string name="cp_start_settings">Launch Settings</string>
@@ -1874,7 +1905,8 @@
This test verifies that if a work profile is locked with a separate password, Recents views
for applications in the work profile are redacted.\n
Some devices may not lock as soon as the screen is turned off by default. On such devices,
- use the button below when requested to lock the work profile.
+ use the button below when requested to lock the work profile. Please skip these tests if
+ "Recents" is absent.
</string>
<string name="provisioning_byod_recents_lock_now">Lock now</string>
@@ -1903,6 +1935,35 @@
The work profile still has a separate password. Please remove this before continuing.
</string>
+ <string name="provisioning_byod_keychain">KeyChain test</string>
+ <string name="provisioning_byod_keychain_info_start">
+ In this test, you\'ll verify that keys generated by KeyChain keys are as usable as keys
+ installed into KeyChain and that they can be hidden from users.\n
+ The test has two parts:\n
+ 1) Testing that a generated key can be selectable by the user.\n
+ 2) Testing that a generated key can be hidden from users.\n
+ \n
+ Tap \"Prepare Test\" button below to begin.\n
+ \n
+ NOTE: A screen lock must be configured for this test. Otherwise, test preparation
+ will fail to generate a key for use by the test.
+ </string>
+ <string name="provisioning_byod_keychain_info_first_test">
+ Once you press \'Go\', a prompt titled \"Choose certificate\" should appear.\n
+ Verify that the list in this dialog has one item, starting with \'cts-verifier-gen\'.
+ Press \'Select\' to select it.\n
+ If the test passes, you\'ll see the text \"Second test ready\" at the bottom.\n
+ \n
+ Press \'Go\'.\n
+ </string>
+ <string name="provisioning_byod_keychain_info_second_test">
+ Once you press \'Run 2nd test\', the same prompt should appear again.\n
+ This time, verify that the title is \"No certificates found\" and the list is empty,
+ then press \'Cancel\'.\n
+ \n
+ Mark the test as passed if the text at the bottom shows \"PASSED (2/2)\"\n
+ </string>
+
<!-- Strings for DeskClock -->
<string name="deskclock_tests">Alarms and Timers Tests</string>
<string name="deskclock_tests_info">
@@ -2662,6 +2723,142 @@
</string>
<string name="device_owner_disable_keyguard_button">Disable keyguard</string>
<string name="device_owner_reenable_keyguard_button">Reenable keyguard</string>
+ <string name="device_owner_lock_task_ui_test">LockTask UI</string>
+ <string name="device_owner_lock_task_ui_test_info">
+ The following tests verify the configurable UI during LockTask, a special mode that
+ prevents the user from leaving the current application.\n\n
+ Please make sure the lock screen is turned on before the test. Press the button below to
+ start LockTask mode. Then mark each item as \'pass\' or \'fail\' according to the
+ instructions.\n\n
+ Finally, execute the last test item to leave LockTask mode.
+ </string>
+ <string name="start_lock_task_button_label">Start LockTask mode</string>
+ <string name="device_owner_lock_task_ui_default_test">Default LockTask UI</string>
+ <string name="device_owner_lock_task_ui_default_test_info">
+ Observe the following UI restrictions. Mark the test as \'pass\' only if ALL of the
+ requirements below are met.\n\n
+ 1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+ battery status, clock, etc.\n
+ 2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+ for the status bar.\n
+ 3) The Home button is hidden.\n
+ 4) The Recents button is hidden.\n
+ 5) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 6) Press the power button to turn off the screen, and press it again to turn the screen
+ back on. Lock screen shouldn\'t be shown.\n
+ 7) The assist gesture isn\'t available.
+ </string>
+ <string name="device_owner_lock_task_ui_system_info_test">Enable system info</string>
+ <string name="device_owner_lock_task_ui_system_info_test_info">
+ Press the button below to enable system info. Observe the system info area of the status
+ bar is now enabled. This includes the clock, connectivity info, battery info, etc.\n\n
+ The rest of the UI restrictions should still apply:\n
+ 1) Notification icons are still hidden on the status bar.\n
+ 2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+ for the status bar.\n
+ 3) The Home button is hidden.\n
+ 4) The Recents button is hidden.\n
+ 5) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 6) Press the power button to turn off the screen, and press it again to turn the screen
+ back on. Lock screen shouldn\'t be shown.\n
+ 7) The assist gesture isn\'t available.\n\n
+ Mark the test as \'pass\' only if ALL of the above requirements are met.
+ </string>
+ <string name="device_owner_lock_task_ui_notifications_test">Enable notifications</string>
+ <string name="device_owner_lock_task_ui_notifications_test_info">
+ Press the button below to enable notifications. Observe the notification icons on the
+ status bar are now enabled. The status bar can also be expanded to show the
+ notifications. However, all Settings UI should remain invisible, including Quick
+ Settings and any link to the Settings app.\n\n
+ The rest of the UI restrictions should still apply:\n
+ 1) System info area is still hidden on the status bar.\n
+ 2) The Home button is hidden.\n
+ 3) The Recents button is hidden.\n
+ 4) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 5) Press the power button to turn off the screen, and press it again to turn the screen
+ back on. Lock screen shouldn\'t be shown.\n
+ 6) The assist gesture isn\'t available.\n\n
+ Mark the test as \'pass\' only if ALL of the above requirements are met.
+ </string>
+ <string name="device_owner_lock_task_ui_home_test">Enable Home button</string>
+ <string name="device_owner_lock_task_ui_home_test_info">
+ Press the button below to enable the Home button. Observe the Home button is now
+ enabled.\n\n
+ The rest of the UI restrictions should still apply:\n
+ 1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+ battery status, clock, etc.\n
+ 2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+ for the status bar.\n
+ 3) The Recents button is hidden.\n
+ 4) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 5) Press the power button to turn off the screen, and press it again to turn the screen
+ back on. Lock screen shouldn\'t be shown.\n
+ 6) The assist gesture isn\'t available.\n\n
+ Mark the test as \'pass\' only if ALL of the above requirements are met.
+ </string>
+ <string name="device_owner_lock_task_ui_recents_test">Enable Recents button</string>
+ <string name="device_owner_lock_task_ui_recents_test_info">
+ Press the button below to enable the Recents button. Observe the Recents button is now
+ enabled. Press the Recents button and verify the Recents view can be opened.\n\n
+ The rest of the UI restrictions should still apply:\n
+ 1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+ battery status, clock, etc.\n
+ 2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+ for the status bar.\n
+ 3) The Home button is hidden.\n
+ 4) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 5) Press the power button to turn off the screen, and press it again to turn the screen
+ back on. Lock screen shouldn\'t be shown.\n
+ 6) The assist gesture isn\'t available.\n\n
+ Mark the test as \'pass\' only if ALL of the above requirements are met.
+ </string>
+ <string name="device_owner_lock_task_ui_global_actions_test">Enable global actions</string>
+ <string name="device_owner_lock_task_ui_global_actions_test_info">
+ Press the button below to enable global actions (a.k.a. power button menu). Long-press
+ the power button and verify a menu containing power-off and restart buttons is shown.
+ This menu can\'t contain any UI that allows the user to change system settings (such as
+ airplane mode switch) or access the Settings app.\n\n
+ The rest of the UI restrictions should still apply:\n
+ 1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+ battery status, clock, etc.\n
+ 2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+ for the status bar.\n
+ 3) The Home button is hidden.\n
+ 4) The Recents button is hidden.\n
+ 5) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 6) The assist gesture isn\'t available.\n\n
+ Mark the test as \'pass\' only if ALL of the above requirements are met.
+ </string>
+ <string name="device_owner_lock_task_ui_keyguard_test">Enable keyguard</string>
+ <string name="device_owner_lock_task_ui_keyguard_test_info">
+ Press the button below to enable keyguard. Press the power button to turn off the screen
+ and press it again to turn the screen back on. Verify that the lock screen is shown.\n\n
+ The rest of the UI restrictions should still apply, both on the lock screen and after
+ the lock screen is dismissed:\n
+ 1) Nothing is shown in the status bar, including notification icons, connectivity icons,
+ battery status, clock, etc.\n
+ 2) The status bar can\'t be expanded. That is, the \'swipe-down\' gesture doesn\'t work
+ for the status bar, even on the lock screen.\n
+ 3) The Home button is hidden.\n
+ 4) The Recents button is hidden.\n
+ 5) Long-press the power button. The power button menu, which usually shows the power-off
+ button, etc., isn\'t shown.\n
+ 6) The assist gesture isn\'t available.\n\n
+ Mark the test as \'pass\' only if ALL of the above requirements are met.
+ </string>
+ <string name="device_owner_lock_task_ui_stop_lock_task_test">Stop LockTask mode</string>
+ <string name="device_owner_lock_task_ui_stop_lock_task_test_info">
+ Press the button below to exit LockTask mode.\n\n
+ Observe that the UI has returned to the normal, unrestricted state, and is no longer
+ subject to any LockTask restriction.\n\n
+ Mark the test as \'pass\' or \'fail\' accordingly.
+ </string>
<string name="device_owner_lockscreen_secure">Please remove lockscreen password</string>
<string name="device_profile_owner_permission_lockdown_test">Permissions lockdown</string>
<string name="device_profile_owner_permission_lockdown_test_instructions">
@@ -2968,7 +3165,9 @@
<string name="disallow_share_location">Disallow share location</string>
<string name="disallow_share_location_action">Turning on location sharing</string>
<string name="disallow_uninstall_apps">Disallow uninstall apps</string>
- <string name="disallow_uninstall_apps_action">Uninstalling applications other CtsVerifier</string>
+ <string name="disallow_uninstall_apps_action">Uninstalling applications from the work profile (badged applications) other than CtsVerifier</string>
+ <string name="disallow_unified_challenge">Disallow unified challenge</string>
+ <string name="disallow_unified_challenge_action">Setting one lock for both personal and work profiles. IMPORTANT: Separate work lock should be set prior to this test in Set work lock test</string>
<string name="disallow_keyguard_unredacted_notifications">Disallow lockscreen unredacted notification</string>
<string name="disallow_keyguard_unredacted_notifications_set_step">Disallow unredacted notifications when device is locked by turning on the switch below</string>
<string name="disallow_keyguard_unredacted_notifications_action">Selecting show all notification content when device is locked</string>
@@ -3014,7 +3213,7 @@
Check that \'Dummy Input method\' is not enabled in Settings and disallow \'Dummy Input method\' from permitted input methods by turning on the switch below.
</string>
<string name="set_permitted_input_methods_action">
- Enabling \'Dummy Input method\' in the list of accessibility services
+ Enabling \'Dummy Input method\' in the list of input methods
</string>
<string name="set_permitted_input_methods_widget_label">
Allow only system input methods:
@@ -3326,6 +3525,23 @@
<string name="comp_provision_profile_dialog_title">Provision work profile</string>
<string name="comp_provision_profile_dialog_text">Press the OK button to start the managed provisioning flow, and complete the flow to create a work profile</string>
+ <string name="managed_user_test">Managed User</string>
+ <string name="managed_user_positive_tests">Managed User positive tests</string>
+ <string name="managed_user_positive_tests_instructions">
+ The positive managed user tests verify policies on a managed user created by a device owner.
+ \n
+ Press Go button to create a managed user, and you will be switched to the managed user
+ automatically. Dismiss the keyguard and a \'Managed User Tests\' should launch.\n
+ Follow the test instructions and press \'pass\' or \'fail\' to return to this screen.\n
+ </string>
+ <string name="managed_user_positive_tests_info">
+ The positive managed user tests verify policies on a managed user created by a device owner.
+ Proceed to the test cases, then press \'pass\' or \'fail\' to finish this test.
+ </string>
+ <string name="managed_user_positive_category">Managed User Tests</string>
+ <string name="managed_user_check_managed_user_test">Check affiliated profile owner</string>
+ <string name="managed_user_incorrect_managed_user">Missing or incorrect affiliated profile owner: CTSVerifier is not affilaited PO!</string>
+
<!-- Strings for JobScheduler Tests -->
<string name="js_test_description">This test is mostly automated, but requires some user interaction. You can pass this test once the list items below are checked.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 8c779c5..8893e0d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -103,7 +103,7 @@
DevicePropertyInfo devicePropertyInfo = new DevicePropertyInfo(Build.CPU_ABI,
Build.CPU_ABI2, abis, abis32, abis64, Build.BOARD, Build.BRAND, Build.DEVICE,
Build.FINGERPRINT, Build.ID, Build.MANUFACTURER, Build.MODEL, Build.PRODUCT,
- referenceFingerprint, Build.SERIAL, Build.TAGS, Build.TYPE, versionBaseOs,
+ referenceFingerprint, Build.getSerial(), Build.TAGS, Build.TYPE, versionBaseOs,
Build.VERSION.RELEASE, Integer.toString(Build.VERSION.SDK_INT),
versionSecurityPatch, Build.VERSION.INCREMENTAL);
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 697ad93..febaf08 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
@@ -16,6 +16,9 @@
package com.android.cts.verifier.camera.its;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -59,6 +62,7 @@
import com.android.ex.camera2.blocking.BlockingSessionCallback;
import com.android.cts.verifier.camera.its.StatsImage;
+import com.android.cts.verifier.R;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -93,6 +97,9 @@
public class ItsService extends Service implements SensorEventListener {
public static final String TAG = ItsService.class.getSimpleName();
+ private final int SERVICE_NOTIFICATION_ID = 37; // random int that is unique within app
+ private NotificationChannel mChannel;
+
// Timeouts, in seconds.
private static final int TIMEOUT_CALLBACK = 20;
private static final int TIMEOUT_3A = 10;
@@ -270,6 +277,15 @@
} catch (ItsException e) {
Logt.e(TAG, "Service failed to start: ", e);
}
+
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ mChannel = new NotificationChannel(
+ "ItsServiceChannel", "ItsService", NotificationManager.IMPORTANCE_LOW);
+ // Configure the notification channel.
+ mChannel.setDescription("ItsServiceChannel");
+ mChannel.enableVibration(false);
+ notificationManager.createNotificationChannel(mChannel);
}
@Override
@@ -285,6 +301,13 @@
} else {
Logt.e(TAG, "Starting ItsService in bad state");
}
+
+ Notification notification = new Notification.Builder(this, mChannel.getId())
+ .setContentTitle("CameraITS Service")
+ .setContentText("CameraITS Service is running")
+ .setSmallIcon(R.drawable.icon)
+ .setOngoing(true).build();
+ startForeground(SERVICE_NOTIFICATION_ID, notification);
} catch (java.lang.InterruptedException e) {
Logt.e(TAG, "Error starting ItsService (interrupted)", e);
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallGNSSTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallGNSSTestsActivity.java
new file mode 100644
index 0000000..1e24e6a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallGNSSTestsActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.verifier.location;
+
+import android.location.cts.GnssMeasurementValuesTest;
+import com.android.cts.verifier.location.base.EmergencyCallBaseTestActivity;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to execute CTS GnssMeasurementValuesTest while dialing emergency number.
+ * It is a wrapper for {@link GnssMeasurementValuesTest} running with AndroidJUnitRunner.
+ */
+public class EmergencyCallGNSSTestsActivity extends EmergencyCallBaseTestActivity {
+ // GNSS test has a longer timeout
+ private static final long PHONE_CALL_DURATION_MS = TimeUnit.MINUTES.toMillis(2);
+
+ public EmergencyCallGNSSTestsActivity() {
+ super(GnssMeasurementValuesTest.class);
+ }
+
+ @Override
+ protected long getPhoneCallDurationMs() {
+ return PHONE_CALL_DURATION_MS;
+ }
+
+ @Override
+ protected boolean showLocalNumberInputbox() {
+ return false;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallMessageTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallMessageTestsActivity.java
new file mode 100644
index 0000000..7377065
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallMessageTestsActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.verifier.location;
+
+import android.location.cts.EmergencyCallMessageTest;
+import com.android.cts.verifier.location.base.EmergencyCallBaseTestActivity;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to execute CTS EmergencyCallMessageTest while dialing emergency number.
+ * It is a wrapper for {@link EmergencyCallMessageTest} running with AndroidJUnitRunner.
+ */
+public class EmergencyCallMessageTestsActivity extends EmergencyCallBaseTestActivity {
+ private static final long PHONE_CALL_DURATION_MS = TimeUnit.SECONDS.toMillis(35);
+ public EmergencyCallMessageTestsActivity() {
+ super(EmergencyCallMessageTest.class);
+ }
+
+ @Override
+ protected long getPhoneCallDurationMs() {
+ return PHONE_CALL_DURATION_MS;
+ }
+
+ @Override
+ protected boolean showLocalNumberInputbox() {
+ return true;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallWifiTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallWifiTestsActivity.java
new file mode 100644
index 0000000..634863d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/EmergencyCallWifiTestsActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.verifier.location;
+
+import android.location.cts.EmergencyCallWifiTest;
+import com.android.cts.verifier.location.base.EmergencyCallBaseTestActivity;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity to execute CTS EmergencyCallWifiTest while dialing emergency number.
+ * It is a wrapper for {@link EmergencyCallWifiTest} running with AndroidJUnitRunner.
+ */
+public class EmergencyCallWifiTestsActivity extends EmergencyCallBaseTestActivity {
+ private static final long PHONE_CALL_DURATION_MS = TimeUnit.SECONDS.toMillis(35);
+ public EmergencyCallWifiTestsActivity() {
+ super(EmergencyCallWifiTest.class);
+ }
+
+ @Override
+ protected long getPhoneCallDurationMs() {
+ return PHONE_CALL_DURATION_MS;
+ }
+
+ @Override
+ protected boolean showLocalNumberInputbox() {
+ return false;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallBaseTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallBaseTestActivity.java
new file mode 100644
index 0000000..ef88e9b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallBaseTestActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.verifier.location.base;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.location.cts.GnssTestCase;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.LayoutInflater;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import com.android.cts.verifier.R;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An Activity that allows Gnss CTS tests to be executed inside CtsVerifier.
+ *
+ * Sub-classes pass the test class as part of construction.
+ * One JUnit test class is executed per Activity, the test class can still be executed outside
+ * CtsVerifier.
+ */
+public abstract class EmergencyCallBaseTestActivity extends GnssCtsTestActivity {
+ private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber";
+ private static final String defaultPhonePackageName = "com.google.android.dialer";
+
+ /**
+ * Constructor for a CTS test executor. It will execute a standalone CTS test class.
+ *
+ * @param testClass The test class to execute, it must be a subclass of {@link AndroidTestCase}.
+ */
+ protected EmergencyCallBaseTestActivity(Class<? extends GnssTestCase> testClass) {
+ super(testClass);
+ }
+
+ protected abstract long getPhoneCallDurationMs();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // override the test info
+ mTextView.setText(R.string.location_emergency_call_test_info);
+ EmergencyCallUtil.setDefaultDialer(this, this.getPackageName());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ EmergencyCallUtil.setDefaultDialer(this, defaultPhonePackageName);
+ }
+
+ protected abstract boolean showLocalNumberInputbox();
+
+ @Override
+ public void onClick(View target) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final FrameLayout frameView = new FrameLayout(this);
+ builder.setView(frameView);
+
+ final boolean enableLocalNumberInputBox = showLocalNumberInputbox();
+ final AlertDialog alertDialog = builder.create();
+ LayoutInflater inflater = alertDialog.getLayoutInflater();
+
+ View dialogView;
+ if (enableLocalNumberInputBox) {
+ dialogView =
+ inflater.inflate(R.layout.emergency_call_msg_test_confirm_dialog, frameView);
+ } else {
+ dialogView = inflater.inflate(R.layout.emergency_call_confirm_dialog, frameView);
+ }
+ final EditText targetNumberEditText =
+ (EditText) dialogView.findViewById(R.id.emergency_number);
+ final Button dialButton = (Button) dialogView.findViewById(R.id.dial_button);
+ dialButton.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (enableLocalNumberInputBox) {
+ final EditText currentNumberEditText =
+ (EditText) dialogView.findViewById(R.id.local_phone_number);
+ String currentNumber = currentNumberEditText.getText().toString();
+ // pass the number to cts tests for cts verifier UI, through System property.
+ System.setProperty(PHONE_NUMBER_KEY, currentNumber);
+ }
+ int targetPhoneNumber =
+ Integer.parseInt(targetNumberEditText.getText().toString());
+ long callDurationMs = EmergencyCallBaseTestActivity.this.getPhoneCallDurationMs();
+ EmergencyCallUtil.makePhoneCall(
+ EmergencyCallBaseTestActivity.this, targetPhoneNumber);
+ EmergencyCallBaseTestActivity.super.onClick(target);
+ EmergencyCallUtil.endCallWithDelay(
+ EmergencyCallBaseTestActivity.this.getApplicationContext(), callDurationMs);
+ alertDialog.dismiss();
+ }
+ });
+ alertDialog.show();
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallUtil.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallUtil.java
new file mode 100644
index 0000000..12b7ac2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/EmergencyCallUtil.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.verifier.location.base;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.text.InputType;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import com.android.cts.verifier.R;
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The EmergencyCallUtil class provides util functions related to the emergency call.
+ */
+public class EmergencyCallUtil {
+ private static final int REQUEST_CODE_SET_DEFAULT_DIALER = 1;
+ private static final String TAG = "EmergencyCallUtil";
+ private static final long WAIT_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(3);
+
+ /*
+ * This method is used to set default dialer app.
+ * To dial 911, it requires to set the dialer to be the system default dial app.
+ *
+ * @param activity current Activity.
+ * @param packageName dialer package name.
+ */
+ public static void setDefaultDialer(Activity activity, String packageName) {
+ final Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
+ intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
+ activity.startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER);
+ }
+
+ public static void makePhoneCall(Activity activity, int phoneNumber) {
+ Intent callIntent = new Intent(Intent.ACTION_CALL);
+ callIntent.setData(Uri.parse("tel:" + phoneNumber));
+ try {
+ callIntent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+ activity.startActivityForResult(callIntent, REQUEST_CODE_SET_DEFAULT_DIALER);
+ } catch (SecurityException ex) {
+ Log.d(TAG, "Failed to make the phone call: " + ex.toString());
+ }
+ // sleep 3sec to make sure call is connected
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(WAIT_FOR_CONNECTION_MS);
+ } catch (InterruptedException ex) {
+ Log.d(TAG, "Failed to make the phone call: " + ex.toString());
+ }
+ }
+ });
+ }
+
+ public static void endCallWithDelay(Context context, long delayMs) {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(delayMs);
+ endCall(context);
+ } catch (InterruptedException ex) {
+ Log.d(TAG, "Failed to make the phone call: " + ex.toString());
+ }
+ }
+ };
+ new Thread(runnable).start();
+ }
+
+ private static void endCall(Context context) {
+ try {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+ Class<?> classTelephony = Class.forName(telephonyManager.getClass().getName());
+ Method methodGetITelephony = classTelephony.getDeclaredMethod("getITelephony");
+ methodGetITelephony.setAccessible(true);
+
+ Object telephonyInterface = methodGetITelephony.invoke(telephonyManager);
+
+ Class<?> telephonyInterfaceClass =
+ Class.forName(telephonyInterface.getClass().getName());
+ Method methodEndCall = telephonyInterfaceClass.getDeclaredMethod("endCall");
+
+ methodEndCall.invoke(telephonyInterface);
+
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to cancel the call: " + e.toString());
+ }
+ }
+
+ private static String getCurrentPhoneNumber(Activity activity) {
+ TelephonyManager tMgr =
+ (TelephonyManager)activity.getSystemService(Context.TELEPHONY_SERVICE);
+ return tMgr.getLine1Number();
+ }
+
+}
\ No newline at end of file
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 15808a7..d7a5033 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -101,6 +101,7 @@
private DialogTestListItem mConfirmWorkCredentials;
private DialogTestListItem mParentProfilePassword;
private TestListItem mVpnTest;
+ private TestListItem mKeyChainTest;
private TestListItem mAlwaysOnVpnSettingsTest;
private TestListItem mRecentsTest;
private TestListItem mDisallowAppsControlTest;
@@ -415,6 +416,12 @@
new Intent(this, OrganizationInfoTestActivity.class),
null);
+ mKeyChainTest = TestListItem.newTest(this,
+ R.string.provisioning_byod_keychain,
+ KeyChainTestActivity.class.getName(),
+ new Intent(KeyChainTestActivity.ACTION_KEYCHAIN),
+ null);
+
mParentProfilePassword = new DialogTestListItem(this,
R.string.provisioning_byod_parent_profile_password,
"BYOD_ParentProfilePasswordTest",
@@ -563,6 +570,7 @@
}
};
adapter.add(mDisableNfcBeamTest);
+ adapter.add(mKeyChainTest);
}
/* If there is an application that handles RECORD_SOUND_ACTION, test that it handles it
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java
index 5053a90..bfa65b7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestHelper.java
@@ -42,7 +42,8 @@
AlwaysOnVpnSettingsTestActivity.class.getName(),
RecentsRedactionActivity.class.getName(),
CommandReceiverActivity.class.getName(),
- SetSupportMessageActivity.class.getName()
+ SetSupportMessageActivity.class.getName(),
+ KeyChainTestActivity.class.getName()
};
for (String component : components) {
mPackageManager.setComponentEnabledSetting(new ComponentName(mContext, component),
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 1d4d13a..5abc2ea 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -16,6 +16,10 @@
package com.android.cts.verifier.managedprovisioning;
+import static android.app.admin.DevicePolicyManager.MAKE_USER_EPHEMERAL;
+import static android.app.admin.DevicePolicyManager.SKIP_SETUP_WIZARD;
+import static android.app.admin.DevicePolicyManager.START_USER_IN_BACKGROUND;
+
import android.Manifest;
import android.app.Activity;
import android.app.KeyguardManager;
@@ -30,24 +34,28 @@
import android.graphics.BitmapFactory;
import android.net.ProxyInfo;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import com.android.cts.verifier.R;
-import com.android.cts.verifier.managedprovisioning.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
public class CommandReceiverActivity extends Activity {
private static final String TAG = "CommandReceiverActivity";
@@ -68,6 +76,7 @@
public static final String COMMAND_SET_KEYGUARD_DISABLED = "set-keyguard-disabled";
public static final String COMMAND_SET_LOCK_SCREEN_INFO = "set-lock-screen-info";
public static final String COMMAND_SET_STATUSBAR_DISABLED = "set-statusbar-disabled";
+ public static final String COMMAND_SET_LOCK_TASK_FEATURES = "set-lock-task-features";
public static final String COMMAND_ALLOW_ONLY_SYSTEM_INPUT_METHODS =
"allow-only-system-input-methods";
public static final String COMMAND_ALLOW_ONLY_SYSTEM_ACCESSIBILITY_SERVICES =
@@ -102,6 +111,7 @@
"clear-maximum-password-attempts";
public static final String COMMAND_SET_DEFAULT_IME = "set-default-ime";
public static final String COMMAND_CLEAR_DEFAULT_IME = "clear-default-ime";
+ public static final String COMMAND_CREATE_MANAGED_USER = "create-managed-user";
public static final String EXTRA_USER_RESTRICTION =
"com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -171,9 +181,8 @@
Context.DEVICE_POLICY_SERVICE);
mUm = (UserManager) getSystemService(Context.USER_SERVICE);
mAdmin = DeviceAdminTestReceiver.getReceiverComponentName();
- Log.i(TAG, "Command: " + intent);
-
final String command = getIntent().getStringExtra(EXTRA_COMMAND);
+ Log.i(TAG, "Command: " + command);
switch (command) {
case COMMAND_SET_USER_RESTRICTION: {
String restrictionKey = intent.getStringExtra(EXTRA_USER_RESTRICTION);
@@ -220,9 +229,15 @@
boolean enforced = intent.getBooleanExtra(EXTRA_ENFORCED, false);
mDpm.setStatusBarDisabled(mAdmin, enforced);
} break;
+ case COMMAND_SET_LOCK_TASK_FEATURES: {
+ int flags = intent.getIntExtra(EXTRA_VALUE,
+ DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+ mDpm.setLockTaskFeatures(mAdmin, flags);
+ } break;
case COMMAND_ALLOW_ONLY_SYSTEM_INPUT_METHODS: {
boolean enforced = intent.getBooleanExtra(EXTRA_ENFORCED, false);
- mDpm.setPermittedInputMethods(mAdmin, enforced ? new ArrayList() : null);
+ mDpm.setPermittedInputMethods(mAdmin,
+ enforced ? getEnabledNonSystemImes() : null);
} break;
case COMMAND_ALLOW_ONLY_SYSTEM_ACCESSIBILITY_SERVICES: {
boolean enforced = intent.getBooleanExtra(EXTRA_ENFORCED, false);
@@ -458,7 +473,20 @@
return;
}
mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD, null);
- }
+ } break;
+ case COMMAND_CREATE_MANAGED_USER:{
+ if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+ return;
+ }
+ PersistableBundle extras = new PersistableBundle();
+ extras.putBoolean(DeviceAdminTestReceiver.EXTRA_MANAGED_USER_TEST, true);
+ UserHandle userHandle = mDpm.createAndManageUser(mAdmin, "managed user", mAdmin,
+ extras,
+ SKIP_SETUP_WIZARD | MAKE_USER_EPHEMERAL | START_USER_IN_BACKGROUND);
+ mDpm.setAffiliationIds(mAdmin,
+ Collections.singleton(DeviceAdminTestReceiver.AFFILIATION_ID));
+ mDpm.switchUser(mAdmin, userHandle);
+ } break;
}
} catch (Exception e) {
Log.e(TAG, "Failed to execute command: " + intent, e);
@@ -562,4 +590,26 @@
mDpm.removeUser(mAdmin, userHandle);
}
}
+
+ public static Intent createSetUserRestrictionIntent(String restriction, boolean enforced) {
+ return new Intent(ACTION_EXECUTE_COMMAND)
+ .putExtra(EXTRA_COMMAND,COMMAND_SET_USER_RESTRICTION)
+ .putExtra(EXTRA_USER_RESTRICTION, restriction)
+ .putExtra(EXTRA_ENFORCED, enforced);
+ }
+
+ private List<String> getEnabledNonSystemImes() {
+ InputMethodManager inputMethodManager = getSystemService(InputMethodManager.class);
+ final List<InputMethodInfo> inputMethods = inputMethodManager.getEnabledInputMethodList();
+ return inputMethods.stream()
+ .filter(inputMethodInfo -> !isSystemInputMethodInfo(inputMethodInfo))
+ .map(inputMethodInfo -> inputMethodInfo.getPackageName())
+ .filter(packageName -> !packageName.equals(getPackageName()))
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ private boolean isSystemInputMethodInfo(InputMethodInfo inputMethodInfo) {
+ return inputMethodInfo.getServiceInfo().applicationInfo.isSystemApp();
+ }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
index f54e567..8486765 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -25,11 +25,14 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PersistableBundle;
+import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.android.cts.verifier.R;
import com.android.cts.verifier.location.LocationListenerActivity;
+import java.util.Collections;
+
/**
* Profile owner receiver for BYOD flow test.
* Setup cross-profile intent filter after successful provisioning.
@@ -43,6 +46,9 @@
DEVICE_OWNER_PKG + ".managedprovisioning.DeviceAdminTestReceiver";
private static final ComponentName RECEIVER_COMPONENT_NAME = new ComponentName(
DEVICE_OWNER_PKG, ADMIN_RECEIVER_TEST_CLASS);
+ public static final String EXTRA_MANAGED_USER_TEST =
+ "com.android.cts.verifier.managedprovisioning.extra.MANAGED_USER_TEST";
+ public static final String AFFILIATION_ID = "affiliationId";
public static ComponentName getReceiverComponentName() {
return RECEIVER_COMPONENT_NAME;
@@ -76,6 +82,32 @@
R.string.bugreport_failed_completing), Utils.BUGREPORT_NOTIFICATION_ID);
}
+ @Override
+ public void onLockTaskModeEntering(Context context, Intent intent, String pkg) {
+ Log.i(TAG, "Entering LockTask mode: " + pkg);
+ LocalBroadcastManager.getInstance(context)
+ .sendBroadcast(new Intent(LockTaskUiTestActivity.ACTION_LOCK_TASK_STARTED));
+ }
+
+ @Override
+ public void onLockTaskModeExiting(Context context, Intent intent) {
+ Log.i(TAG, "Exiting LockTask mode");
+ LocalBroadcastManager.getInstance(context)
+ .sendBroadcast(new Intent(LockTaskUiTestActivity.ACTION_LOCK_TASK_STOPPED));
+ }
+
+ @Override
+ public void onEnabled(Context context, Intent intent) {
+ Log.i(TAG, "Device admin enabled");
+ if (intent.getBooleanExtra(EXTRA_MANAGED_USER_TEST, false)) {
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ ComponentName admin = DeviceAdminTestReceiver.getReceiverComponentName();
+ dpm.setAffiliationIds(admin,
+ Collections.singleton(DeviceAdminTestReceiver.AFFILIATION_ID));
+ context.startActivity(new Intent(context, ManagedUserPositiveTestActivity.class));
+ }
+ }
+
private void setupProfile(Context context) {
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.setProfileEnabled(new ComponentName(context.getApplicationContext(), getClass()));
@@ -117,6 +149,7 @@
filter.addAction(ByodHelperActivity.ACTION_SET_ORGANIZATION_INFO);
filter.addAction(ByodHelperActivity.ACTION_TEST_PARENT_PROFILE_PASSWORD);
filter.addAction(SetSupportMessageActivity.ACTION_SET_SUPPORT_MSG);
+ filter.addAction(KeyChainTestActivity.ACTION_KEYCHAIN);
filter.addAction(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
dpm.addCrossProfileIntentFilter(getWho(context), filter,
DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 0cae6be..6c87b84 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -21,7 +21,6 @@
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -29,12 +28,10 @@
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
-import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import com.android.cts.verifier.ArrayTestListAdapter;
-import com.android.cts.verifier.IntentDrivenTestActivity;
import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
@@ -60,6 +57,7 @@
private static final String WIFI_LOCKDOWN_TEST_ID = WifiLockdownTestActivity.class.getName();
private static final String DISABLE_STATUS_BAR_TEST_ID = "DISABLE_STATUS_BAR";
private static final String DISABLE_KEYGUARD_TEST_ID = "DISABLE_KEYGUARD";
+ private static final String LOCK_TASK_UI_TEST_ID = "LOCK_TASK_UI";
private static final String CHECK_PERMISSION_LOCKDOWN_TEST_ID =
PermissionLockdownTestActivity.class.getName();
private static final String DISALLOW_CONFIG_BT_ID = "DISALLOW_CONFIG_BT";
@@ -72,7 +70,8 @@
private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
private static final String ENTERPRISE_PRIVACY_TEST_ID = "ENTERPRISE_PRIVACY";
private static final String NETWORK_LOGGING_UI_TEST_ID = "NETWORK_LOGGING_UI";
- public static final String COMP_TEST_ID = "COMP_UI";
+ private static final String COMP_TEST_ID = "COMP_UI";
+ private static final String MANAGED_USER_TEST_ID = "MANAGED_USER_UI";
private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
@Override
@@ -171,8 +170,8 @@
new ButtonInfo[] {
new ButtonInfo(
R.string.device_owner_user_restriction_set,
- createSetUserRestrictionIntent(
- UserManager.DISALLOW_CONFIG_WIFI)),
+ CommandReceiverActivity.createSetUserRestrictionIntent(
+ UserManager.DISALLOW_CONFIG_WIFI, true)),
new ButtonInfo(
R.string.device_owner_settings_go,
new Intent(Settings.ACTION_WIFI_SETTINGS))}));
@@ -185,8 +184,8 @@
new ButtonInfo[] {
new ButtonInfo(
R.string.device_owner_user_vpn_restriction_set,
- createSetUserRestrictionIntent(
- UserManager.DISALLOW_CONFIG_VPN)),
+ CommandReceiverActivity.createSetUserRestrictionIntent(
+ UserManager.DISALLOW_CONFIG_VPN, true)),
new ButtonInfo(
R.string.device_owner_settings_go,
new Intent(Settings.ACTION_VPN_SETTINGS)),
@@ -202,8 +201,8 @@
new ButtonInfo[] {
new ButtonInfo(
R.string.device_owner_user_restriction_set,
- createSetUserRestrictionIntent(
- UserManager.DISALLOW_DATA_ROAMING)),
+ CommandReceiverActivity.createSetUserRestrictionIntent(
+ UserManager.DISALLOW_DATA_ROAMING, true)),
new ButtonInfo(
R.string.device_owner_settings_go,
new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))}));
@@ -216,8 +215,8 @@
new ButtonInfo[] {
new ButtonInfo(
R.string.device_owner_user_restriction_set,
- createSetUserRestrictionIntent(
- UserManager.DISALLOW_FACTORY_RESET))}));
+ CommandReceiverActivity.createSetUserRestrictionIntent(
+ UserManager.DISALLOW_FACTORY_RESET, true))}));
// DISALLOW_CONFIG_BLUETOOTH
if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
@@ -227,8 +226,8 @@
new ButtonInfo[] {
new ButtonInfo(
R.string.device_owner_user_restriction_set,
- createSetUserRestrictionIntent(
- UserManager.DISALLOW_CONFIG_BLUETOOTH)),
+ CommandReceiverActivity.createSetUserRestrictionIntent(
+ UserManager.DISALLOW_CONFIG_BLUETOOTH, true)),
new ButtonInfo(
R.string.device_owner_settings_go,
new Intent(Settings.ACTION_BLUETOOTH_SETTINGS))}));
@@ -241,8 +240,8 @@
new ButtonInfo[] {
new ButtonInfo(
R.string.device_owner_user_restriction_set,
- createSetUserRestrictionIntent(
- UserManager.DISALLOW_USB_FILE_TRANSFER)),
+ CommandReceiverActivity.createSetUserRestrictionIntent(
+ UserManager.DISALLOW_USB_FILE_TRANSFER, true)),
}));
// DISABLE_STATUS_BAR_TEST
@@ -279,6 +278,14 @@
CommandReceiverActivity.COMMAND_SET_KEYGUARD_DISABLED,
false))}));
+ // setLockTaskFeatures
+ final Intent lockTaskUiTestIntent = new Intent(this, LockTaskUiTestActivity.class);
+ lockTaskUiTestIntent.putExtra(LockTaskUiTestActivity.EXTRA_TEST_ID, LOCK_TASK_UI_TEST_ID);
+ adapter.add(createTestItem(this, LOCK_TASK_UI_TEST_ID,
+ R.string.device_owner_lock_task_ui_test,
+ lockTaskUiTestIntent));
+
+ // setUserIcon
adapter.add(createInteractiveTestItem(this, SET_USER_ICON_TEST_ID,
R.string.device_owner_set_user_icon,
R.string.device_owner_set_user_icon_instruction,
@@ -317,6 +324,7 @@
R.string.enterprise_privacy_test,
enterprisePolicyTestIntent));
+ // COMP
if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
Intent compIntent = new Intent(this, CompTestActivity.class)
.putExtra(PolicyTransparencyTestActivity.EXTRA_TEST_ID, COMP_TEST_ID);
@@ -325,6 +333,18 @@
compIntent));
}
+ // Managed user
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)
+ && UserManager.supportsMultipleUsers()) {
+ adapter.add(createInteractiveTestItem(this, MANAGED_USER_TEST_ID,
+ R.string.managed_user_test,
+ R.string.managed_user_positive_tests_instructions,
+ new ButtonInfo[]{
+ new ButtonInfo(
+ R.string.device_owner_settings_go,
+ createCreateManagedUserIntent())}));
+ }
+
// Network logging UI
adapter.add(createInteractiveTestItem(this, NETWORK_LOGGING_UI_TEST_ID,
R.string.device_owner_network_logging_ui,
@@ -365,14 +385,6 @@
.putExtra(CommandReceiverActivity.EXTRA_ENFORCED, value);
}
- private Intent createSetUserRestrictionIntent(String restriction) {
- return new Intent(this, CommandReceiverActivity.class)
- .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
- CommandReceiverActivity.COMMAND_SET_USER_RESTRICTION)
- .putExtra(CommandReceiverActivity.EXTRA_USER_RESTRICTION, restriction)
- .putExtra(CommandReceiverActivity.EXTRA_ENFORCED, true);
- }
-
private Intent createSetUserIconIntent() {
return new Intent(this, CommandReceiverActivity.class)
.putExtra(CommandReceiverActivity.EXTRA_COMMAND,
@@ -391,6 +403,12 @@
CommandReceiverActivity.COMMAND_DISABLE_NETWORK_LOGGING);
}
+ private Intent createCreateManagedUserIntent() {
+ return new Intent(this, CommandReceiverActivity.class)
+ .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+ CommandReceiverActivity.COMMAND_CREATE_MANAGED_USER);
+ }
+
private boolean isStatusBarEnabled() {
// Watches don't support the status bar so this is an ok proxy, but this is not the most
// general test for that. TODO: add a test API to do a real check for status bar support.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
index 71c4421..3a16297 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -269,19 +269,18 @@
CommandReceiverActivity.
COMMAND_REMOVE_MANAGED_PROFILE))}));
}
- // Disabled for API 26 due to b/63696536.
- // adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
- // R.string.enterprise_privacy_failed_password_wipe,
- // R.string.enterprise_privacy_failed_password_wipe_info,
- // new ButtonInfo[] {
- // new ButtonInfo(R.string.enterprise_privacy_open_settings,
- // new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
- // new ButtonInfo(R.string.enterprise_privacy_set_limit,
- // buildCommandIntent(CommandReceiverActivity
- // .COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
- // new ButtonInfo(R.string.enterprise_privacy_finish,
- // buildCommandIntent(CommandReceiverActivity
- // .COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS))}));
+ adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
+ R.string.enterprise_privacy_failed_password_wipe,
+ R.string.enterprise_privacy_failed_password_wipe_info,
+ new ButtonInfo[] {
+ new ButtonInfo(R.string.enterprise_privacy_open_settings,
+ new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
+ new ButtonInfo(R.string.enterprise_privacy_set_limit,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
+ new ButtonInfo(R.string.enterprise_privacy_finish,
+ buildCommandIntent(CommandReceiverActivity
+ .COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS))}));
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
adapter.add(createInteractiveTestItem(this,
ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
new file mode 100644
index 0000000..a59261c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/KeyChainTestActivity.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2018 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.managedprovisioning;
+
+import static android.keystore.cts.CertificateUtils.createCertificate;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import java.security.GeneralSecurityException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Activity to test KeyChain key generation. The following flows are tested: * Generating a key. *
+ * Installing a (self-signed) certificate associated with the key, visible to users. * Setting
+ * visibility of the certificate to not be visible to user.
+ *
+ * <p>After the key generation and certificate installation, it should be possible for a user to
+ * select the key from the certificate selection prompt when {@code KeyChain.choosePrivateKeyAlias}
+ * is called. The test then tests that the key is indeed usable for signing.
+ *
+ * <p>After the visibility is set to not-user-visible, the prompt is shown again, this time the
+ * testes is asked to verify no keys are selectable and cancel the dialog.
+ */
+public class KeyChainTestActivity extends PassFailButtons.Activity {
+ private static final String TAG = "ByodKeyChainActivity";
+
+ public static final String ACTION_KEYCHAIN =
+ "com.android.cts.verifier.managedprovisioning.KEYCHAIN";
+
+ public static final String ALIAS = "cts-verifier-gen-rsa-1";
+ public static final String KEY_ALGORITHM = "RSA";
+
+ private DevicePolicyManager mDevicePolicyManager;
+ private AttestedKeyPair mAttestedKeyPair;
+ private X509Certificate mCert;
+ private TextView mLogView;
+ private TextView mInstructionsView;
+ private Button mSetupButton;
+ private Button mGoButton;
+
+ // Callback interface for when a key is generated.
+ static interface KeyGenerationListener {
+ void onKeyPairGenerated(AttestedKeyPair keyPair);
+ }
+
+ // Task for generating a key pair using {@code DevicePolicyManager.generateKeyPair}.
+ // The listener, if provided, will be invoked after the key has been generated successfully.
+ class GenerateKeyTask extends AsyncTask<KeyGenParameterSpec, Integer, AttestedKeyPair> {
+ KeyGenerationListener mListener;
+
+ public GenerateKeyTask(KeyGenerationListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ protected AttestedKeyPair doInBackground(KeyGenParameterSpec... specs) {
+ Log.i(TAG, "Generating key pair.");
+ try {
+ AttestedKeyPair kp =
+ mDevicePolicyManager.generateKeyPair(
+ DeviceAdminTestReceiver.getReceiverComponentName(),
+ KEY_ALGORITHM,
+ specs[0],
+ 0);
+ if (kp != null) {
+ mLogView.setText("Key generated successfully.");
+ } else {
+ mLogView.setText("Failed generating key.");
+ }
+ return kp;
+ } catch (SecurityException e) {
+ mLogView.setText("Security exception while generating key.");
+ Log.w(TAG, "Security exception", e);
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(AttestedKeyPair kp) {
+ super.onPostExecute(kp);
+ if (mListener != null && kp != null) {
+ mListener.onKeyPairGenerated(kp);
+ }
+ }
+ }
+
+ // Helper for generating and installing a self-signed certificate.
+ class CertificateInstaller implements KeyGenerationListener {
+ @Override
+ public void onKeyPairGenerated(AttestedKeyPair keyPair) {
+ mAttestedKeyPair = keyPair;
+ X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
+ X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
+ try {
+ mCert = createCertificate(mAttestedKeyPair.getKeyPair(), subject, issuer);
+ boolean installResult = installCertificate(mCert, true);
+ // called from onPostExecute so safe to interact with the UI here.
+ if (installResult) {
+ mLogView.setText("Test ready");
+ mInstructionsView.setText(R.string.provisioning_byod_keychain_info_first_test);
+ mGoButton.setEnabled(true);
+ } else {
+ mLogView.setText("FAILED certificate installation.");
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Failed installing certificate", e);
+ mLogView.setText("Error generating a certificate.");
+ }
+ }
+ }
+
+ // Helper for calling {@code DevicePolicyManager.setKeyPairCertificate} with the user-visibility
+ // specified in the constructor. Returns true if the call was successful (and no exceptions
+ // were thrown).
+ protected boolean installCertificate(X509Certificate cert, boolean isUserVisible) {
+ try {
+ return mDevicePolicyManager.setKeyPairCertificate(
+ DeviceAdminTestReceiver.getReceiverComponentName(),
+ ALIAS,
+ Arrays.asList(new X509Certificate[] {cert}),
+ isUserVisible);
+ } catch (SecurityException e) {
+ logStatus("Security exception while installing cert.");
+ Log.w(TAG, "Security exception", e);
+ }
+ return false;
+ }
+
+ // Invokes choosePrivateKeyAlias.
+ void selectCertificate(KeyChainAliasCallback callback) {
+ String[] keyTypes = new String[] {KEY_ALGORITHM};
+ Principal[] issuers = new Principal[0];
+ KeyChain.choosePrivateKeyAlias(
+ KeyChainTestActivity.this, callback, keyTypes, issuers, null, null);
+ }
+
+ class TestPreparator implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ mLogView.setText("Starting key generation");
+ KeyGenParameterSpec spec =
+ new KeyGenParameterSpec.Builder(
+ ALIAS,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setKeySize(2048)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setSignaturePaddings(
+ KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+ .build();
+ new GenerateKeyTask(new CertificateInstaller()).execute(spec);
+ }
+ }
+
+ class SelectCertificate implements View.OnClickListener, KeyChainAliasCallback {
+ @Override
+ public void onClick(View v) {
+ Log.i(TAG, "Selecting certificate");
+ mLogView.setText("Waiting for prompt");
+ selectCertificate(this);
+ }
+
+ @Override
+ public void alias(String alias) {
+ Log.i(TAG, "Got alias: " + alias);
+ if (alias == null) {
+ logStatus("FAILED (no alias)");
+ return;
+ } else if (!alias.equals(ALIAS)) {
+ logStatus("FAILED (wrong alias)");
+ return;
+ }
+ logStatus("Got right alias.");
+ try {
+ PrivateKey privateKey = KeyChain.getPrivateKey(KeyChainTestActivity.this, alias);
+ byte[] data = new String("hello").getBytes();
+ Signature sign = Signature.getInstance("SHA256withRSA");
+ sign.initSign(privateKey);
+ sign.update(data);
+ if (sign.sign() != null) {
+ prepareSecondTest();
+ } else {
+ logStatus("FAILED (cannot sign)");
+ }
+ } catch (GeneralSecurityException | KeyChainException | InterruptedException e) {
+ Log.w(TAG, "Failed using the key", e);
+ logStatus("FAILED (key unusable)");
+ }
+ }
+ }
+
+ class SelectCertificateExpectingNone implements View.OnClickListener, KeyChainAliasCallback {
+ @Override
+ public void onClick(View v) {
+ Log.i(TAG, "Selecting certificate");
+ mLogView.setText("Waiting for prompt");
+ selectCertificate(this);
+ }
+
+ @Override
+ public void alias(String alias) {
+ Log.i(TAG, "Got alias: " + alias);
+ if (alias != null) {
+ logStatus("FAILED: Should have no certificate.");
+ } else {
+ logStatus("PASSED (2/2)");
+ runOnUiThread(
+ () -> {
+ getPassButton().setEnabled(true);
+ });
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.keychain_test);
+ setPassFailButtonClickListeners();
+ mDevicePolicyManager =
+ (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ mLogView = (TextView) findViewById(R.id.provisioning_byod_keychain_test_log);
+ mLogView.setMovementMethod(new ScrollingMovementMethod());
+
+ mInstructionsView = (TextView) findViewById(R.id.provisioning_byod_keychain_instructions);
+
+ mSetupButton = (Button) findViewById(R.id.prepare_test_button);
+ mSetupButton.setOnClickListener(new TestPreparator());
+
+ mGoButton = (Button) findViewById(R.id.run_test_button);
+ mGoButton.setOnClickListener(new SelectCertificate());
+ mGoButton.setEnabled(false);
+
+ // Disable the pass button here, only enable it when the 2nd test passes.
+ getPassButton().setEnabled(false);
+ }
+
+ protected void prepareSecondTest() {
+ Runnable uiChanges;
+ if (installCertificate(mCert, false)) {
+ uiChanges =
+ () -> {
+ mLogView.setText("Second test ready.");
+ mInstructionsView.setText(
+ R.string.provisioning_byod_keychain_info_second_test);
+ mGoButton.setText("Run 2nd test");
+ mGoButton.setOnClickListener(new SelectCertificateExpectingNone());
+ };
+ } else {
+ uiChanges =
+ () -> {
+ mLogView.setText("FAILED second test setup.");
+ mGoButton.setEnabled(false);
+ };
+ }
+
+ runOnUiThread(uiChanges);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ try {
+ mDevicePolicyManager.removeKeyPair(
+ DeviceAdminTestReceiver.getReceiverComponentName(), ALIAS);
+ Log.i(TAG, "Deleted alias " + ALIAS);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Failed deleting alias", e);
+ }
+ }
+
+ private void logStatus(String status) {
+ runOnUiThread(
+ () -> {
+ mLogView.setText(status);
+ });
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
new file mode 100644
index 0000000..a6e6b2f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
+
+import static com.android.cts.verifier.managedprovisioning.Utils.createInteractiveTestItem;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.DataSetObserver;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+import com.android.cts.verifier.TestResult;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)}.
+ */
+public class LockTaskUiTestActivity extends PassFailButtons.TestListActivity {
+
+ private static final String TAG = LockTaskUiTestActivity.class.getSimpleName();
+
+ public static final String EXTRA_TEST_ID =
+ "com.android.cts.verifier.managedprovisioning.extra.TEST_ID";
+
+ /** Broadcast action sent by {@link DeviceAdminTestReceiver} when LockTask starts. */
+ static final String ACTION_LOCK_TASK_STARTED =
+ "com.android.cts.verifier.managedprovisioning.action.LOCK_TASK_STARTED";
+ /** Broadcast action sent by {@link DeviceAdminTestReceiver} when LockTask stops. */
+ static final String ACTION_LOCK_TASK_STOPPED =
+ "com.android.cts.verifier.managedprovisioning.action.LOCK_TASK_STOPPED";
+
+ private static final ComponentName ADMIN_RECEIVER =
+ DeviceAdminTestReceiver.getReceiverComponentName();
+ private static final String TEST_PACKAGE_NAME = "com.android.cts.verifier";
+ private static final String ACTION_STOP_LOCK_TASK =
+ "com.android.cts.verifier.managedprovisioning.action.STOP_LOCK_TASK";
+
+ private static final String TEST_ID_DEFAULT = "lock-task-ui-default";
+ private static final String TEST_ID_SYSTEM_INFO = "lock-task-ui-system-info";
+ private static final String TEST_ID_NOTIFICATIONS = "lock-task-ui-notifications";
+ private static final String TEST_ID_HOME = "lock-task-ui-home";
+ private static final String TEST_ID_RECENTS = "lock-task-ui-recents";
+ private static final String TEST_ID_GLOBAL_ACTIONS = "lock-task-ui-global-actions";
+ private static final String TEST_ID_KEYGUARD = "lock-task-ui-keyguard";
+ private static final String TEST_ID_STOP_LOCK_TASK = "lock-task-ui-stop-lock-task";
+
+ private DevicePolicyManager mDpm;
+ private ActivityManager mAm;
+ private NotificationManager mNotifyMgr;
+
+ private LockTaskStateChangedReceiver mStateChangedReceiver;
+ private CountDownLatch mLockTaskStartedLatch;
+ private CountDownLatch mLockTaskStoppedLatch;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.device_owner_lock_task_ui);
+ setPassFailButtonClickListeners();
+
+ mDpm = getSystemService(DevicePolicyManager.class);
+ mAm = getSystemService(ActivityManager.class);
+ mNotifyMgr = getSystemService(NotificationManager.class);
+
+ final ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+ addTestsToAdapter(adapter);
+ adapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ updatePassButton();
+ }
+ });
+ setTestListAdapter(adapter);
+
+ Button startLockTaskButton = findViewById(R.id.start_lock_task_button);
+ startLockTaskButton.setOnClickListener((view) -> startLockTaskMode());
+
+ if (ACTION_STOP_LOCK_TASK.equals(getIntent().getAction())) {
+ // This means we're started by the "stop LockTask mode" test activity (the last one in
+ // the list) in order to stop LockTask.
+ stopLockTaskMode();
+ }
+ }
+
+ private void addTestsToAdapter(final ArrayTestListAdapter adapter) {
+ adapter.add(createInteractiveTestItem(this,
+ TEST_ID_DEFAULT,
+ R.string.device_owner_lock_task_ui_default_test,
+ R.string.device_owner_lock_task_ui_default_test_info,
+ new ButtonInfo[]{}));
+
+ adapter.add(createSetLockTaskFeaturesTest(
+ TEST_ID_SYSTEM_INFO,
+ LOCK_TASK_FEATURE_SYSTEM_INFO,
+ R.string.device_owner_lock_task_ui_system_info_test,
+ R.string.device_owner_lock_task_ui_system_info_test_info));
+
+ adapter.add(createSetLockTaskFeaturesTest(
+ TEST_ID_NOTIFICATIONS,
+ LOCK_TASK_FEATURE_NOTIFICATIONS,
+ R.string.device_owner_lock_task_ui_notifications_test,
+ R.string.device_owner_lock_task_ui_notifications_test_info));
+
+ adapter.add(createSetLockTaskFeaturesTest(
+ TEST_ID_HOME,
+ LOCK_TASK_FEATURE_HOME,
+ R.string.device_owner_lock_task_ui_home_test,
+ R.string.device_owner_lock_task_ui_home_test_info));
+
+ adapter.add(createSetLockTaskFeaturesTest(
+ TEST_ID_RECENTS,
+ LOCK_TASK_FEATURE_RECENTS,
+ R.string.device_owner_lock_task_ui_recents_test,
+ R.string.device_owner_lock_task_ui_recents_test_info));
+
+ adapter.add(createSetLockTaskFeaturesTest(
+ TEST_ID_GLOBAL_ACTIONS,
+ LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+ R.string.device_owner_lock_task_ui_global_actions_test,
+ R.string.device_owner_lock_task_ui_global_actions_test_info));
+
+ adapter.add(createSetLockTaskFeaturesTest(
+ TEST_ID_KEYGUARD,
+ LOCK_TASK_FEATURE_KEYGUARD,
+ R.string.device_owner_lock_task_ui_keyguard_test,
+ R.string.device_owner_lock_task_ui_keyguard_test_info));
+
+ final Intent stopLockTaskIntent = new Intent(this, LockTaskUiTestActivity.class);
+ stopLockTaskIntent.setAction(ACTION_STOP_LOCK_TASK);
+ adapter.add(createInteractiveTestItem(this,
+ TEST_ID_STOP_LOCK_TASK,
+ R.string.device_owner_lock_task_ui_stop_lock_task_test,
+ R.string.device_owner_lock_task_ui_stop_lock_task_test_info,
+ new ButtonInfo(
+ R.string.device_owner_lock_task_ui_stop_lock_task_test,
+ stopLockTaskIntent
+ )));
+ }
+
+ /** Receives LockTask start/stop callbacks forwarded by {@link DeviceAdminTestReceiver}. */
+ private final class LockTaskStateChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ switch (action) {
+ case ACTION_LOCK_TASK_STARTED:
+ if (mLockTaskStartedLatch != null) {
+ mLockTaskStartedLatch.countDown();
+ }
+ break;
+ case ACTION_LOCK_TASK_STOPPED:
+ if (mLockTaskStoppedLatch != null) {
+ mLockTaskStoppedLatch.countDown();
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mStateChangedReceiver = new LockTaskStateChangedReceiver();
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_LOCK_TASK_STARTED);
+ filter.addAction(ACTION_LOCK_TASK_STOPPED);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mStateChangedReceiver, filter);
+ }
+
+ @Override
+ protected void onPause() {
+ if (mStateChangedReceiver != null) {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mStateChangedReceiver);
+ mStateChangedReceiver = null;
+ }
+ super.onPause();
+ }
+
+ /**
+ * Starts LockTask mode and waits for callback from {@link DeviceAdminTestReceiver} to confirm
+ * LockTask has started successfully. If the callback isn't received, the entire test will be
+ * marked as failed.
+ *
+ * @see LockTaskStateChangedReceiver
+ */
+ private void startLockTaskMode() {
+ if (mAm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ return;
+ }
+
+ mLockTaskStartedLatch = new CountDownLatch(1);
+ try {
+ mDpm.setLockTaskPackages(ADMIN_RECEIVER, new String[] {TEST_PACKAGE_NAME});
+ mDpm.setLockTaskFeatures(ADMIN_RECEIVER, LOCK_TASK_FEATURE_NONE);
+ startLockTask();
+
+ new CheckLockTaskStateTask() {
+ @Override
+ protected void onPostExecute(Boolean success) {
+ if (success) {
+ issueTestNotification();
+ } else {
+ notifyFailure(getTestId(), "Failed to start LockTask mode");
+ }
+ }
+ }.execute(mLockTaskStartedLatch);
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getMessage(), e);
+ Toast.makeText(this, "Failed to run test. Did you set up device owner correctly?",
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * Stops LockTask mode and waits for callback from {@link DeviceAdminTestReceiver} to confirm
+ * LockTask has stopped successfully. If the callback isn't received, the "Stop LockTask mode"
+ * test case will be marked as failed.
+ *
+ * Note that we {@link #finish()} this activity here, since it's started by the "Stop LockTask
+ * mode" test activity, and shouldn't be exposed to the tester once its job is done.
+ *
+ * @see LockTaskStateChangedReceiver
+ */
+ private void stopLockTaskMode() {
+ if (mAm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_NONE) {
+ finish();
+ return;
+ }
+
+ mLockTaskStoppedLatch = new CountDownLatch(1);
+ try {
+ stopLockTask();
+
+ new CheckLockTaskStateTask() {
+ @Override
+ protected void onPostExecute(Boolean success) {
+ if (!success) {
+ notifyFailure(TEST_ID_STOP_LOCK_TASK, "Failed to stop LockTask mode");
+ }
+ cancelTestNotification();
+ mDpm.setLockTaskFeatures(ADMIN_RECEIVER, LOCK_TASK_FEATURE_NONE);
+ mDpm.setLockTaskPackages(ADMIN_RECEIVER, new String[] {});
+ LockTaskUiTestActivity.this.finish();
+ }
+ }.execute(mLockTaskStoppedLatch);
+ } catch (SecurityException e) {
+ Log.e(TAG, e.getMessage(), e);
+ Toast.makeText(this, "Failed to finish test. Did you set up device owner correctly?",
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private abstract class CheckLockTaskStateTask extends AsyncTask<CountDownLatch, Void, Boolean> {
+ @Override
+ protected Boolean doInBackground(CountDownLatch... latches) {
+ if (latches.length > 0 && latches[0] != null) {
+ try {
+ return latches[0].await(1, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ // Fall through
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected abstract void onPostExecute(Boolean success);
+ }
+
+ private void notifyFailure(String testId, String message) {
+ Log.e(TAG, message);
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ TestResult.setFailedResult(this, testId, message);
+ }
+
+ private void issueTestNotification() {
+ String channelId = getTestId();
+ if (mNotifyMgr.getNotificationChannel(channelId) == null) {
+ NotificationChannel channel = new NotificationChannel(
+ channelId, getTestId(), NotificationManager.IMPORTANCE_HIGH);
+ mNotifyMgr.createNotificationChannel(channel);
+ }
+
+ Notification note = new Notification.Builder(this, channelId)
+ .setContentTitle(getString(R.string.device_owner_lock_task_ui_test))
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setOngoing(true)
+ .build();
+
+ mNotifyMgr.notify(0, note);
+ }
+
+ private void cancelTestNotification() {
+ mNotifyMgr.cancelAll();
+ }
+
+ private TestListItem createSetLockTaskFeaturesTest(String testId, int featureFlags,
+ int titleResId, int detailResId) {
+ final Intent commandIntent = new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
+ commandIntent.putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+ CommandReceiverActivity.COMMAND_SET_LOCK_TASK_FEATURES);
+ commandIntent.putExtra(CommandReceiverActivity.EXTRA_VALUE, featureFlags);
+
+ return createInteractiveTestItem(this, testId, titleResId, detailResId,
+ new ButtonInfo(titleResId, commandIntent));
+ }
+
+ @Override
+ public String getTestId() {
+ return getIntent().getStringExtra(EXTRA_TEST_ID);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
new file mode 100644
index 0000000..1ce0807
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+import static com.android.cts.verifier.managedprovisioning.Utils.createInteractiveTestItem;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.DataSetObserver;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
+import com.android.cts.verifier.TestResult;
+
+/**
+ * Activity that lists all positive managed user tests.
+ */
+public class ManagedUserPositiveTestActivity extends PassFailButtons.TestListActivity {
+ private static final String TAG = "ManagedUserPositiveTestActivity";
+
+ private static final String ACTION_CHECK_AFFILIATED_PROFILE_OWNER =
+ "com.android.cts.verifier.managedprovisioning.action.CHECK_AFFILIATED_PROFILE_OWNER";
+ static final String EXTRA_TEST_ID = "extra-test-id";
+
+ private static final String CHECK_AFFILIATED_PROFILE_OWNER_TEST_ID =
+ "CHECK_AFFILIATED_PROFILE_OWNER";
+ private static final String DEVICE_ADMIN_SETTINGS_ID = "DEVICE_ADMIN_SETTINGS";
+ private static final String DISABLE_STATUS_BAR_TEST_ID = "DISABLE_STATUS_BAR";
+ private static final String DISABLE_KEYGUARD_TEST_ID = "DISABLE_KEYGUARD";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (ACTION_CHECK_AFFILIATED_PROFILE_OWNER.equals(getIntent().getAction())) {
+ DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ if (dpm.isProfileOwnerApp(getPackageName()) && dpm.isAffiliatedUser()) {
+ TestResult.setPassedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
+ null, null);
+ } else {
+ TestResult.setFailedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
+ getString(R.string.managed_user_incorrect_managed_user), null);
+ }
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.positive_managed_user);
+ setInfoResources(R.string.managed_user_positive_tests,
+ R.string.managed_user_positive_tests_info, 0);
+ setPassFailButtonClickListeners();
+
+ final ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
+ adapter.add(TestListItem.newCategory(this, R.string.managed_user_positive_category));
+
+ addTestsToAdapter(adapter);
+
+ adapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ updatePassButton();
+ }
+ });
+
+ setTestListAdapter(adapter);
+ }
+
+ @Override
+ public void finish() {
+ // If this activity was started for checking profile owner status, then no need to do any
+ // tear down.
+ if (!ACTION_CHECK_AFFILIATED_PROFILE_OWNER.equals(getIntent().getAction())) {
+ // Pass and fail buttons are known to call finish() when clicked,
+ // and this is when we want to remove the managed owner.
+ DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ dpm.logoutUser(DeviceAdminTestReceiver.getReceiverComponentName());
+ }
+ super.finish();
+ }
+
+ private void addTestsToAdapter(final ArrayTestListAdapter adapter) {
+ adapter.add(createTestItem(this, CHECK_AFFILIATED_PROFILE_OWNER_TEST_ID,
+ R.string.managed_user_check_managed_user_test,
+ new Intent(ACTION_CHECK_AFFILIATED_PROFILE_OWNER)
+ .putExtra(EXTRA_TEST_ID, getIntent().getStringExtra(EXTRA_TEST_ID))));
+
+ // device admin settings
+ adapter.add(createInteractiveTestItem(this, DEVICE_ADMIN_SETTINGS_ID,
+ R.string.device_owner_device_admin_visible,
+ R.string.device_owner_device_admin_visible_info,
+ new ButtonInfo(
+ R.string.device_owner_settings_go,
+ new Intent(Settings.ACTION_SECURITY_SETTINGS))));
+
+ // DISABLE_STATUS_BAR_TEST
+ if (isStatusBarEnabled()) {
+ adapter.add(createInteractiveTestItem(this, DISABLE_STATUS_BAR_TEST_ID,
+ R.string.device_owner_disable_statusbar_test,
+ R.string.device_owner_disable_statusbar_test_info,
+ new ButtonInfo[]{
+ new ButtonInfo(
+ R.string.device_owner_disable_statusbar_button,
+ createManagedUserIntentWithBooleanParameter(
+ CommandReceiverActivity.COMMAND_SET_STATUSBAR_DISABLED,
+ true)),
+ new ButtonInfo(
+ R.string.device_owner_reenable_statusbar_button,
+ createManagedUserIntentWithBooleanParameter(
+ CommandReceiverActivity.COMMAND_SET_STATUSBAR_DISABLED,
+ false))}));
+ }
+
+ // setKeyguardDisabled
+ adapter.add(createInteractiveTestItem(this, DISABLE_KEYGUARD_TEST_ID,
+ R.string.device_owner_disable_keyguard_test,
+ R.string.device_owner_disable_keyguard_test_info,
+ new ButtonInfo[]{
+ new ButtonInfo(
+ R.string.device_owner_disable_keyguard_button,
+ createManagedUserIntentWithBooleanParameter(
+ CommandReceiverActivity.COMMAND_SET_KEYGUARD_DISABLED,
+ true)),
+ new ButtonInfo(
+ R.string.device_owner_reenable_keyguard_button,
+ createManagedUserIntentWithBooleanParameter(
+ CommandReceiverActivity.COMMAND_SET_KEYGUARD_DISABLED,
+ false))}));
+ }
+
+
+ static TestListItem createTestItem(Activity activity, String id, int titleRes,
+ Intent intent) {
+ intent.putExtra(EXTRA_TEST_ID, id);
+ return TestListItem.newTest(activity, titleRes, id, intent, null);
+ }
+
+ private Intent createManagedUserIntentWithBooleanParameter(String command, boolean value) {
+ return new Intent(this, CommandReceiverActivity.class)
+ .putExtra(CommandReceiverActivity.EXTRA_COMMAND, command)
+ .putExtra(CommandReceiverActivity.EXTRA_ENFORCED, value);
+ }
+
+ private boolean isStatusBarEnabled() {
+ // Watches don't support the status bar so this is an ok proxy, but this is not the most
+ // general test for that. TODO: add a test API to do a real check for status bar support.
+ return !getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
index 0f57b87..e3d6edb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestActivity.java
@@ -21,7 +21,6 @@
import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
-import android.os.UserManager;
import android.util.ArrayMap;
import android.view.accessibility.AccessibilityEvent;
import android.view.View;
@@ -235,15 +234,14 @@
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- final Intent intent = new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
+ final Intent intent;
if (TEST_CHECK_USER_RESTRICTION.equals(mTest)) {
final String userRestriction = getIntent().getStringExtra(
CommandReceiverActivity.EXTRA_USER_RESTRICTION);
- intent.putExtra(CommandReceiverActivity.EXTRA_COMMAND,
- CommandReceiverActivity.COMMAND_SET_USER_RESTRICTION);
- intent.putExtra(CommandReceiverActivity.EXTRA_USER_RESTRICTION, userRestriction);
- intent.putExtra(CommandReceiverActivity.EXTRA_ENFORCED, isChecked);
+ intent = CommandReceiverActivity.createSetUserRestrictionIntent(
+ userRestriction, isChecked);
} else {
+ intent = new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND);
final PolicyTestItem testItem = POLICY_TEST_ITEMS.get(mTest);
intent.putExtra(CommandReceiverActivity.EXTRA_COMMAND, testItem.command);
intent.putExtra(CommandReceiverActivity.EXTRA_ENFORCED, isChecked);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 2c44030..cb25bc6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -49,7 +49,8 @@
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
UserManager.DISALLOW_REMOVE_USER,
UserManager.DISALLOW_SHARE_LOCATION,
- UserManager.DISALLOW_UNINSTALL_APPS
+ UserManager.DISALLOW_UNINSTALL_APPS,
+ UserManager.DISALLOW_UNIFIED_PASSWORD,
};
private static final ArrayMap<String, UserRestrictionItem> USER_RESTRICTION_ITEMS;
@@ -73,7 +74,8 @@
R.string.disallow_remove_managed_profile,
R.string.disallow_remove_user,
R.string.disallow_share_location,
- R.string.disallow_uninstall_apps
+ R.string.disallow_uninstall_apps,
+ R.string.disallow_unified_challenge,
};
final int[] restrictionActions = new int[] {
@@ -95,7 +97,8 @@
R.string.disallow_remove_managed_profile_action,
R.string.disallow_remove_user_action,
R.string.disallow_share_location_action,
- R.string.disallow_uninstall_apps_action
+ R.string.disallow_uninstall_apps_action,
+ R.string.disallow_unified_challenge_action,
};
final String[] settingsIntentActions = new String[] {
@@ -118,6 +121,7 @@
Settings.ACTION_SETTINGS,
Settings.ACTION_LOCATION_SOURCE_SETTINGS,
Settings.ACTION_APPLICATION_SETTINGS,
+ Settings.ACTION_SECURITY_SETTINGS,
};
if (RESTRICTION_IDS_FOR_POLICY_TRANSPARENCY.length != restrictionLabels.length
@@ -143,6 +147,7 @@
ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_UNINSTALL_APPS);
ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_MODIFY_ACCOUNTS);
ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_SHARE_LOCATION);
+ ALSO_VALID_FOR_PO_POLICY_TRANSPARENCY.add(UserManager.DISALLOW_UNIFIED_PASSWORD);
}
public static String getRestrictionLabel(Context context, String restriction) {
@@ -168,7 +173,8 @@
ArrayList<String> result = new ArrayList<String>();
// They are all valid except for DISALLOW_REMOVE_MANAGED_PROFILE
for (String st : RESTRICTION_IDS_FOR_POLICY_TRANSPARENCY) {
- if (!st.equals(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE)) {
+ if (!st.equals(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE)
+ && !st.equals(UserManager.DISALLOW_UNIFIED_PASSWORD)) {
result.add(st);
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
index 6a1ce2c..29c8708 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -26,6 +26,7 @@
import android.content.ContentProviderOperation;
import android.content.OperationApplicationException;
import android.database.Cursor;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
@@ -35,7 +36,9 @@
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;
@@ -50,6 +53,8 @@
private static final String NOTIFICATION_CHANNEL_ID = TAG;
private static final String NOTIFICATION_CHANNEL_ID_NOISY = TAG + "/noisy";
+ private static final String NOTIFICATION_CHANNEL_ID_MEDIA = TAG + "/media";
+ private static final String NOTIFICATION_CHANNEL_ID_GAME = TAG + "/game";
private static final String ALICE = "Alice";
private static final String ALICE_PHONE = "+16175551212";
private static final String ALICE_EMAIL = "alice@_foo._bar";
@@ -91,9 +96,13 @@
tests.add(new IsEnabledTest());
tests.add(new ServiceStartedTest());
tests.add(new InsertContactsTest());
- tests.add(new NoneInterceptsAllTest());
- tests.add(new PriorityInterceptsSomeTest());
- tests.add(new AllInterceptsNothingTest());
+ tests.add(new NoneInterceptsAllMessagesTest());
+ tests.add(new NoneInterceptsAlarmEventReminderCategoriesTest());
+ tests.add(new PriorityInterceptsSomeMessagesTest());
+ tests.add(new PriorityInterceptsAlarmsTest());
+ tests.add(new PriorityInterceptsMediaSystemOtherTest());
+ tests.add(new AllInterceptsNothingMessagesTest());
+ tests.add(new AllInterceptsNothingDiffCategoriesTest());
tests.add(new DefaultOrderTest());
tests.add(new PriorityOrderTest());
tests.add(new InterruptionOrderTest());
@@ -113,11 +122,25 @@
NOTIFICATION_CHANNEL_ID_NOISY, NotificationManager.IMPORTANCE_HIGH);
noisyChannel.enableVibration(true);
mNm.createNotificationChannel(noisyChannel);
+ NotificationChannel mediaChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_MEDIA,
+ NOTIFICATION_CHANNEL_ID_MEDIA, NotificationManager.IMPORTANCE_HIGH);
+ AudioAttributes.Builder aa = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA);
+ mediaChannel.setSound(null, aa.build());
+ mNm.createNotificationChannel(mediaChannel);
+ NotificationChannel gameChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_GAME,
+ NOTIFICATION_CHANNEL_ID_GAME, NotificationManager.IMPORTANCE_HIGH);
+ AudioAttributes.Builder aa2 = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_GAME);
+ gameChannel.setSound(null, aa2.build());
+ mNm.createNotificationChannel(gameChannel);
}
private void deleteChannels() {
mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_NOISY);
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_MEDIA);
+ mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID_GAME);
}
// Tests
@@ -185,7 +208,7 @@
}
}
- protected class NoneInterceptsAllTest extends InteractiveTestCase {
+ protected class NoneInterceptsAllMessagesTest extends InteractiveTestCase {
@Override
View inflate(ViewGroup parent) {
return createAutoItem(parent, R.string.attention_all_are_filtered);
@@ -213,7 +236,7 @@
try {
String tag = payload.getString(JSON_TAG);
boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+ Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
if (found.contains(tag)) {
// multiple entries for same notification!
pass = false;
@@ -238,24 +261,23 @@
@Override
void tearDown() {
- mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
mNm.cancelAll();
deleteChannels();
MockListener.getInstance().resetData();
}
-
}
- protected class AllInterceptsNothingTest extends InteractiveTestCase {
+ protected class NoneInterceptsAlarmEventReminderCategoriesTest extends InteractiveTestCase {
@Override
View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.attention_none_are_filtered);
+ return createAutoItem(parent, R.string.attention_all_are_filtered);
}
@Override
void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
createChannels();
- sendNotifications(MODE_URI, false, false);
+ sendEventAlarmReminderNotifications(SEND_ALL);
status = READY;
}
@@ -273,7 +295,66 @@
try {
String tag = payload.getString(JSON_TAG);
boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+ 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;
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ deleteChannels();
+ MockListener.getInstance().resetData();
+ }
+ }
+
+ protected class AllInterceptsNothingMessagesTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_none_are_filtered_messages);
+ }
+
+ @Override
+ void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ createChannels();
+ sendNotifications(MODE_URI, false, false); // different messages
+ status = READY;
+ }
+
+ @Override
+ void test() {
+ List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+ Set<String> found = new HashSet<String>();
+ if (result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (JSONObject payload : result) {
+ try {
+ 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;
@@ -304,18 +385,77 @@
}
}
- protected class PriorityInterceptsSomeTest extends InteractiveTestCase {
+ protected class AllInterceptsNothingDiffCategoriesTest extends InteractiveTestCase {
@Override
View inflate(ViewGroup parent) {
- return createAutoItem(parent, R.string.attention_some_are_filtered);
+ return createAutoItem(parent, R.string.attention_none_are_filtered_diff_categories);
+ }
+
+ @Override
+ void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ createChannels();
+ sendEventAlarmReminderNotifications(SEND_ALL);
+ status = READY;
+ }
+
+ @Override
+ void test() {
+ List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+ Set<String> found = new HashSet<String>();
+ if (result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (JSONObject payload : result) {
+ try {
+ 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;
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ deleteChannels();
+ MockListener.getInstance().resetData();
+ }
+ }
+
+ protected class PriorityInterceptsSomeMessagesTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_some_are_filtered_messages);
}
@Override
void setUp() {
mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
NotificationManager.Policy policy = mNm.getNotificationPolicy();
- policy = new NotificationManager.Policy(policy.priorityCategories
- | NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES,
+ policy = new NotificationManager.Policy(
+ NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES,
policy.priorityCallSenders,
NotificationManager.Policy.PRIORITY_SENDERS_STARRED);
mNm.setNotificationPolicy(policy);
@@ -338,7 +478,7 @@
try {
String tag = payload.getString(JSON_TAG);
boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
- Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+ Log.e(TAG, tag + (!zen ? "" : " not") + " intercepted");
if (found.contains(tag)) {
// multiple entries for same notification!
pass = false;
@@ -357,13 +497,144 @@
Log.e(TAG, "failed to unpack data from mocklistener", e);
}
}
- pass &= found.size() == 3;
+ pass &= found.size() >= 3;
status = pass ? PASS : FAIL;
}
@Override
void tearDown() {
- mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ mNm.cancelAll();
+ deleteChannels();
+ MockListener.getInstance().resetData();
+ }
+ }
+
+ protected class PriorityInterceptsAlarmsTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_some_are_filtered_alarms);
+ }
+
+ @Override
+ void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ NotificationManager.Policy policy = mNm.getNotificationPolicy();
+ policy = new NotificationManager.Policy(
+ NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS,
+ policy.priorityCallSenders,
+ policy.priorityMessageSenders);
+ mNm.setNotificationPolicy(policy);
+ createChannels();
+ // Event to Alice, Alarm to Bob, Reminder to Charlie:
+ sendEventAlarmReminderNotifications(SEND_ALL);
+ status = READY;
+ }
+
+ @Override
+ void test() {
+ List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+ Set<String> found = new HashSet<String>();
+ if (result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (JSONObject payload : result) {
+ try {
+ String tag = payload.getString(JSON_TAG);
+ boolean zenIntercepted = !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+ Log.e(TAG, tag + (zenIntercepted ? "" : " not") + " intercepted");
+ if (found.contains(tag)) {
+ // multiple entries for same notification!
+ pass = false;
+ } else if (ALICE.equals(tag)) {
+ found.add(ALICE);
+ pass &= zenIntercepted; // Alice's event notif should be intercepted
+ } else if (BOB.equals(tag)) {
+ found.add(BOB);
+ pass &= !zenIntercepted; // Bob's alarm notif should not be intercepted
+ } else if (CHARLIE.equals(tag)) {
+ found.add(CHARLIE);
+ pass &= zenIntercepted; // Charlie's reminder notif should be intercepted
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+ pass &= found.size() >= 3;
+ status = pass ? PASS : FAIL;
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ deleteChannels();
+ MockListener.getInstance().resetData();
+ }
+ }
+
+ protected class PriorityInterceptsMediaSystemOtherTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.attention_some_are_filtered_media_system_other);
+ }
+
+ @Override
+ void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ NotificationManager.Policy policy = mNm.getNotificationPolicy();
+ policy = new NotificationManager.Policy(
+ NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
+ policy.priorityCallSenders,
+ policy.priorityMessageSenders);
+ mNm.setNotificationPolicy(policy);
+ createChannels();
+ // Alarm to Alice, Other (Game) to Bob, Media to Charlie:
+ sendAlarmOtherMediaNotifications(SEND_ALL);
+ status = READY;
+ }
+
+ @Override
+ void test() {
+ List<JSONObject> result = new ArrayList<>(MockListener.getInstance().getPosted());
+
+ Set<String> found = new HashSet<String>();
+ if (result.size() == 0) {
+ status = FAIL;
+ return;
+ }
+ boolean pass = true;
+ for (JSONObject payload : result) {
+ try {
+ String tag = payload.getString(JSON_TAG);
+ boolean zenIntercepted = !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+ Log.e(TAG, tag + (zenIntercepted ? "" : " not") + " intercepted");
+ if (found.contains(tag)) {
+ // multiple entries for same notification!
+ pass = false;
+ } else if (ALICE.equals(tag)) {
+ found.add(ALICE);
+ pass &= zenIntercepted;
+ } else if (BOB.equals(tag)) {
+ found.add(BOB);
+ pass &= !zenIntercepted;
+ } else if (CHARLIE.equals(tag)) {
+ found.add(CHARLIE);
+ pass &= !zenIntercepted;
+ }
+ } catch (JSONException e) {
+ pass = false;
+ Log.e(TAG, "failed to unpack data from mocklistener", e);
+ }
+ }
+ pass &= found.size() >= 3;
+ status = pass ? PASS : FAIL;
+ }
+
+ @Override
+ void tearDown() {
mNm.cancelAll();
deleteChannels();
MockListener.getInstance().resetData();
@@ -379,6 +650,7 @@
@Override
void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
createChannels();
sendNotifications(MODE_NONE, false, false);
status = READY;
@@ -415,6 +687,7 @@
@Override
void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
createChannels();
sendNotifications(MODE_NONE, true, false);
status = READY;
@@ -453,6 +726,7 @@
@Override
void setUp() {
+ mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
delayTime = 15000;
createChannels();
// send B & C noisy with contact affinity
@@ -727,6 +1001,83 @@
}
}
+ private void sendEventAlarmReminderNotifications(int which) {
+ long when = System.currentTimeMillis() - 4000000L;
+ final String channelId = NOTIFICATION_CHANNEL_ID;
+
+ // Event notification to Alice
+ if ((which & SEND_A) != 0) {
+ Notification.Builder alice = new Notification.Builder(mContext, channelId)
+ .setContentTitle(ALICE)
+ .setContentText(ALICE)
+ .setSmallIcon(R.drawable.ic_stat_alice)
+ .setCategory(Notification.CATEGORY_EVENT)
+ .setWhen(when);
+ mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build());
+ }
+
+ // Alarm notification to Bob
+ if ((which & SEND_B) != 0) {
+ Notification.Builder bob = new Notification.Builder(mContext, channelId)
+ .setContentTitle(BOB)
+ .setContentText(BOB)
+ .setSmallIcon(R.drawable.ic_stat_bob)
+ .setCategory(Notification.CATEGORY_ALARM)
+ .setWhen(when);
+ mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build());
+ }
+
+ // Reminder notification to Charlie
+ if ((which & SEND_C) != 0) {
+ Notification.Builder charlie =
+ new Notification.Builder(mContext, channelId)
+ .setContentTitle(CHARLIE)
+ .setContentText(CHARLIE)
+ .setSmallIcon(R.drawable.ic_stat_charlie)
+ .setCategory(Notification.CATEGORY_REMINDER)
+ .setWhen(when);
+ mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build());
+ }
+ }
+
+ private void sendAlarmOtherMediaNotifications(int which) {
+ long when = System.currentTimeMillis() - 4000000L;
+ final String channelId = NOTIFICATION_CHANNEL_ID;
+
+ // Alarm notification to Alice
+ if ((which & SEND_A) != 0) {
+ Notification.Builder alice = new Notification.Builder(mContext, channelId)
+ .setContentTitle(ALICE)
+ .setContentText(ALICE)
+ .setSmallIcon(R.drawable.ic_stat_alice)
+ .setCategory(Notification.CATEGORY_ALARM)
+ .setWhen(when);
+ mNm.notify(ALICE, NOTIFICATION_ID + 1, alice.build());
+ }
+
+ // "Other" notification to Bob
+ if ((which & SEND_B) != 0) {
+ Notification.Builder bob = new Notification.Builder(mContext,
+ NOTIFICATION_CHANNEL_ID_GAME)
+ .setContentTitle(BOB)
+ .setContentText(BOB)
+ .setSmallIcon(R.drawable.ic_stat_bob)
+ .setWhen(when);
+ mNm.notify(BOB, NOTIFICATION_ID + 2, bob.build());
+ }
+
+ // Media notification to Charlie
+ if ((which & SEND_C) != 0) {
+ Notification.Builder charlie =
+ new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID_MEDIA)
+ .setContentTitle(CHARLIE)
+ .setContentText(CHARLIE)
+ .setSmallIcon(R.drawable.ic_stat_charlie)
+ .setWhen(when);
+ mNm.notify(CHARLIE, NOTIFICATION_ID + 3, charlie.build());
+ }
+ }
+
private void addPerson(int mode, Notification.Builder note,
Uri uri, String phone, String email) {
if (mode == MODE_URI && uri != null) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
new file mode 100644
index 0000000..f432ddb
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
@@ -0,0 +1,27 @@
+package com.android.cts.verifier.notifications;
+
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+public class BlockChangeReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SharedPreferences prefs = context.getSharedPreferences(
+ NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+ if (ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED.equals(intent.getAction())
+ || ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED.equals(intent.getAction())) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(
+ intent.getStringExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID),
+ intent.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false));
+ editor.commit();
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
index f3bf7e5..285855f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
@@ -18,15 +18,18 @@
import com.android.cts.verifier.R;
+import android.app.ActivityManager;
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcelable;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -56,17 +59,25 @@
@Override
protected List<InteractiveTestCase> createTestItems() {
List<InteractiveTestCase> tests = new ArrayList<>(9);
- tests.add(new IsEnabledTest());
- tests.add(new ServiceStartedTest());
- tests.add(new CreateAutomaticZenRuleTest());
- tests.add(new UpdateAutomaticZenRuleTest());
- tests.add(new GetAutomaticZenRuleTest());
- tests.add(new GetAutomaticZenRulesTest());
- tests.add(new SubscribeAutomaticZenRuleTest());
- tests.add(new DeleteAutomaticZenRuleTest());
- tests.add(new UnsubscribeAutomaticZenRuleTest());
- tests.add(new IsDisabledTest());
- tests.add(new ServiceStoppedTest());
+ ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ if (am.isLowRamDevice()) {
+ tests.add(new CannotBeEnabledTest());
+ tests.add(new ServiceStoppedTest());
+ } else {
+ tests.add(new IsEnabledTest());
+ tests.add(new ServiceStartedTest());
+ tests.add(new CreateAutomaticZenRuleTest());
+ tests.add(new UpdateAutomaticZenRuleTest());
+ tests.add(new GetAutomaticZenRuleTest());
+ tests.add(new GetAutomaticZenRulesTest());
+ tests.add(new SubscribeAutomaticZenRuleTest());
+ tests.add(new DeleteAutomaticZenRuleTest());
+ tests.add(new UnsubscribeAutomaticZenRuleTest());
+ tests.add(new RequestUnbindTest());
+ tests.add(new RequestBindTest());
+ tests.add(new IsDisabledTest());
+ tests.add(new ServiceStoppedTest());
+ }
return tests;
}
@@ -101,6 +112,50 @@
// wait for the service to start
delay();
}
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+ }
+ }
+
+ protected class CannotBeEnabledTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createNlsSettingsItem(parent, R.string.cp_cannot_enable_service);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ void test() {
+ mNm.cancelAll();
+ Intent settings = new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+ if (settings.resolveActivity(mPackageManager) == null) {
+ logFail("no settings activity");
+ status = FAIL;
+ } else {
+ if (mNm.isNotificationPolicyAccessGranted()) {
+ status = FAIL;
+ } else {
+ status = PASS;
+ }
+ next();
+ }
+ }
+
+ void tearDown() {
+ // wait for the service to start
+ delay();
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+ }
}
protected class ServiceStartedTest extends InteractiveTestCase {
@@ -135,6 +190,75 @@
}
}
+ private class RequestUnbindTest extends InteractiveTestCase {
+ int mRetries = 5;
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_snooze);
+
+ }
+
+ @Override
+ void setUp() {
+ status = READY;
+ }
+
+ @Override
+ void test() {
+ if (status == READY) {
+ MockConditionProvider.getInstance().requestUnbind();
+ status = RETEST;
+ } else {
+ if (MockConditionProvider.getInstance() == null ||
+ !MockConditionProvider.getInstance().isConnected()) {
+ status = PASS;
+ } else {
+ if (--mRetries > 0) {
+ status = RETEST;
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ }
+ next();
+ }
+ }
+ }
+
+ private class RequestBindTest extends InteractiveTestCase {
+ int mRetries = 5;
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_unsnooze);
+
+ }
+
+ @Override
+ void test() {
+ if (status == READY) {
+ MockConditionProvider.requestRebind(MockConditionProvider.COMPONENT_NAME);
+ status = RETEST;
+ } else {
+ if (MockConditionProvider.getInstance().isConnected()
+ && MockConditionProvider.getInstance().isBound()) {
+ status = PASS;
+ next();
+ } else {
+ if (--mRetries > 0) {
+ status = RETEST;
+ next();
+ } else {
+ logFail();
+ status = FAIL;
+ }
+ }
+ }
+ }
+ }
+
+
private class CreateAutomaticZenRuleTest extends InteractiveTestCase {
private String id = null;
@@ -516,6 +640,11 @@
MockConditionProvider.resetData(mContext);
delay();
}
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
+ }
}
private class ServiceStoppedTest extends InteractiveTestCase {
@@ -570,21 +699,4 @@
protected View createSettingsItem(ViewGroup parent, int messageId) {
return createUserItem(parent, R.string.cp_start_settings, messageId);
}
-
- public void launchSettings() {
- startActivity(new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
- }
-
- public void actionPressed(View v) {
- Object tag = v.getTag();
- if (tag instanceof Integer) {
- int id = ((Integer) tag).intValue();
- if (id == R.string.cp_start_settings) {
- launchSettings();
- } else if (id == R.string.attention_ready) {
- mCurrentTest.status = READY;
- next();
- }
- }
- }
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 7d5de8c..0ffd77d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -16,6 +16,8 @@
package com.android.cts.verifier.notifications;
+import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -65,8 +67,6 @@
// 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;
@@ -133,6 +133,12 @@
Log.e(TAG, "failed " + this.getClass().getSimpleName() +
((message == null) ? "" : ": " + message), e);
}
+
+ // If this test contains a button that launches another activity, override this
+ // method to provide the intent to launch.
+ protected Intent getIntent() {
+ return null;
+ }
}
abstract int getTitleResource();
@@ -345,16 +351,12 @@
// 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();
+ if (mCurrentTest != null && mCurrentTest.getIntent() != null) {
+ startActivity(mCurrentTest.getIntent());
} else if (id == R.string.attention_ready) {
if (mCurrentTest != null) {
mCurrentTest.status = READY;
@@ -452,7 +454,7 @@
@Override
void test() {
mNm.cancelAll();
- Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
+ Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
if (settings.resolveActivity(mPackageManager) == null) {
logFail("no settings activity");
status = FAIL;
@@ -468,10 +470,57 @@
}
}
+ @Override
void tearDown() {
// wait for the service to start
delay();
}
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+ }
+ }
+
+ protected class CannotBeEnabledTest extends InteractiveTestCase {
+ @Override
+ View inflate(ViewGroup parent) {
+ return createNlsSettingsItem(parent, R.string.nls_cannot_enable_service);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ void test() {
+ mNm.cancelAll();
+ Intent settings = new Intent(ACTION_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 = FAIL;
+ } else {
+ status = PASS;
+ }
+ next();
+ }
+ }
+
+ void tearDown() {
+ // wait for the service to start
+ delay();
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+ }
}
protected class ServiceStartedTest extends InteractiveTestCase {
@@ -482,7 +531,7 @@
@Override
void test() {
- if (MockListener.getInstance() != null) {
+ if (MockListener.getInstance() != null && MockListener.getInstance().isConnected) {
status = PASS;
next();
} else {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java
index 489797b..f32460f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockConditionProvider.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.app.AutomaticZenRule;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -35,7 +36,8 @@
public class MockConditionProvider extends ConditionProviderService {
static final String TAG = "MockConditionProvider";
- static final String PACKAGE_NAME = "com.android.cts.verifier.notifications";
+ public static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.android.cts.verifier", MockConditionProvider.class.getName());
static final String PATH = "mock_cp";
static final String QUERY = "query_item";
@@ -56,6 +58,7 @@
private ArrayList<Uri> mSubscriptions = new ArrayList<>();
private boolean mConnected = false;
private BroadcastReceiver mReceiver;
+ private static MockConditionProvider sConditionProviderInstance = null;
@Override
public void onCreate() {
@@ -103,6 +106,20 @@
unregisterReceiver(mReceiver);
mReceiver = null;
Log.d(TAG, "destroyed");
+ sConditionProviderInstance = null;
+ }
+
+ public void requestUnbindService() {
+ sConditionProviderInstance = null;
+ super.requestUnbind();
+ }
+
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ public static MockConditionProvider getInstance() {
+ return sConditionProviderInstance;
}
public void resetData() {
@@ -141,6 +158,7 @@
public void onConnected() {
Log.d(TAG, "connected");
mConnected = true;
+ sConditionProviderInstance = this;
}
@Override
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 8b5acf8..5dfee12 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -18,6 +18,7 @@
import android.app.Notification;
import android.content.ComponentName;
import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Log;
@@ -47,6 +48,7 @@
public static final String JSON_AMBIENT = "ambient";
public static final String JSON_MATCHES_ZEN_FILTER = "matches_zen_filter";
public static final String JSON_REASON = "reason";
+ public static final String JSON_STATS = "stats";
ArrayList<String> mPosted = new ArrayList<String>();
ArrayMap<String, JSONObject> mNotifications = new ArrayMap<>();
@@ -174,13 +176,14 @@
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
- int reason) {
+ NotificationStats stats, int reason) {
Log.d(TAG, "removed: " + sbn.getTag() + " for reason " + reason);
mRemoved.add(sbn.getTag());
JSONObject removed = new JSONObject();
try {
removed.put(JSON_TAG, sbn.getTag());
removed.put(JSON_REASON, reason);
+ removed.put(JSON_STATS, stats != null);
} catch (JSONException e) {
Log.e(TAG, "failed to pack up notification payload", e);
}
@@ -189,5 +192,4 @@
mRemovedReason.put(sbn.getTag(), removed);
onNotificationRankingUpdate(rankingMap);
}
-
}
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 062b8a9..90b1f7d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -16,20 +16,30 @@
package com.android.cts.verifier.notifications;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+import static android.provider.Settings.EXTRA_APP_PACKAGE;
+import static android.provider.Settings.EXTRA_CHANNEL_GROUP_ID;
+import static android.provider.Settings.EXTRA_CHANNEL_ID;
+
import android.annotation.SuppressLint;
-import android.app.Activity;
+import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.notification.StatusBarNotification;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
import com.android.cts.verifier.R;
@@ -45,11 +55,13 @@
import static com.android.cts.verifier.notifications.MockListener.*;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
implements Runnable {
private static final String TAG = "NoListenerVerifier";
private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ protected static final String PREFS = "listener_prefs";
private String mTag1;
private String mTag2;
@@ -82,30 +94,40 @@
@Override
protected List<InteractiveTestCase> createTestItems() {
List<InteractiveTestCase> tests = new ArrayList<>(17);
- tests.add(new IsEnabledTest());
- tests.add(new ServiceStartedTest());
- tests.add(new NotificationReceivedTest());
- tests.add(new DataIntactTest());
- tests.add(new DismissOneTest());
- tests.add(new DismissOneWithReasonTest());
- tests.add(new DismissAllTest());
- tests.add(new SnoozeNotificationForTimeTest());
- tests.add(new SnoozeNotificationForTimeCancelTest());
- tests.add(new GetSnoozedNotificationTest());
- tests.add(new EnableHintsTest());
- tests.add(new RequestUnbindTest());
- tests.add(new RequestBindTest());
- tests.add(new MessageBundleTest());
- tests.add(new EnableHintsTest());
- tests.add(new IsDisabledTest());
- tests.add(new ServiceStoppedTest());
- tests.add(new NotificationNotReceivedTest());
+ ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ if (am.isLowRamDevice()) {
+ tests.add(new CannotBeEnabledTest());
+ tests.add(new ServiceStoppedTest());
+ tests.add(new NotificationNotReceivedTest());
+ } else {
+ tests.add(new IsEnabledTest());
+ tests.add(new ServiceStartedTest());
+ tests.add(new NotificationReceivedTest());
+ tests.add(new DataIntactTest());
+ tests.add(new DismissOneTest());
+ tests.add(new DismissOneWithReasonTest());
+ tests.add(new DismissOneWithStatsTest());
+ tests.add(new DismissAllTest());
+ tests.add(new SnoozeNotificationForTimeTest());
+ tests.add(new SnoozeNotificationForTimeCancelTest());
+ tests.add(new GetSnoozedNotificationTest());
+ tests.add(new EnableHintsTest());
+ tests.add(new ReceiveChannelBlockNoticeTest());
+ tests.add(new ReceiveGroupBlockNoticeTest());
+ tests.add(new RequestUnbindTest());
+ tests.add(new RequestBindTest());
+ tests.add(new MessageBundleTest());
+ tests.add(new EnableHintsTest());
+ tests.add(new IsDisabledTest());
+ tests.add(new ServiceStoppedTest());
+ tests.add(new NotificationNotReceivedTest());
+ }
return tests;
}
private void createChannel() {
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
- NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+ NOTIFICATION_CHANNEL_ID, IMPORTANCE_LOW);
mNm.createNotificationChannel(channel);
}
@@ -206,6 +228,161 @@
}
}
+ /**
+ * Creates a notification channel. Sends the user to settings to block the channel. Waits
+ * to receive the broadcast that the channel was blocked, and confirms that the broadcast
+ * contains the correct extras.
+ */
+ protected class ReceiveChannelBlockNoticeTest extends InteractiveTestCase {
+ private String mChannelId;
+ private int mRetries = 2;
+ private View mView;
+ @Override
+ View inflate(ViewGroup parent) {
+ mView = createNlsSettingsItem(parent, R.string.nls_block_channel);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(false);
+ return mView;
+ }
+
+ @Override
+ void setUp() {
+ mChannelId = UUID.randomUUID().toString();
+ NotificationChannel channel = new NotificationChannel(
+ mChannelId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
+ mNm.createNotificationChannel(channel);
+ status = READY;
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(true);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ void test() {
+ NotificationChannel channel = mNm.getNotificationChannel(mChannelId);
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+
+ if (channel.getImportance() == IMPORTANCE_NONE) {
+ if (prefs.contains(mChannelId) && prefs.getBoolean(mChannelId, false)) {
+ status = PASS;
+ } else {
+ if (mRetries > 0) {
+ mRetries--;
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
+ }
+ } else {
+ // user hasn't jumped to settings to block the channel yet
+ status = WAIT_FOR_USER;
+ }
+
+ next();
+ }
+
+ void tearDown() {
+ MockListener.getInstance().resetData();
+ mNm.deleteNotificationChannel(mChannelId);
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(mChannelId);
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
+ .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
+ .putExtra(EXTRA_CHANNEL_ID, mChannelId);
+ }
+ }
+
+ /**
+ * Creates a notification channel group. Sends the user to settings to block the group. Waits
+ * to receive the broadcast that the group was blocked, and confirms that the broadcast contains
+ * the correct extras.
+ */
+ protected class ReceiveGroupBlockNoticeTest extends InteractiveTestCase {
+ private String mGroupId;
+ private int mRetries = 2;
+ private View mView;
+ @Override
+ View inflate(ViewGroup parent) {
+ mView = createNlsSettingsItem(parent, R.string.nls_block_group);
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(false);
+ return mView;
+ }
+
+ @Override
+ void setUp() {
+ mGroupId = UUID.randomUUID().toString();
+ NotificationChannelGroup group
+ = new NotificationChannelGroup(mGroupId, "ReceiveChannelGroupBlockNoticeTest");
+ mNm.createNotificationChannelGroup(group);
+ NotificationChannel channel = new NotificationChannel(
+ mGroupId, "ReceiveChannelBlockNoticeTest", IMPORTANCE_LOW);
+ channel.setGroup(mGroupId);
+ mNm.createNotificationChannel(channel);
+ status = READY;
+ Button button = mView.findViewById(R.id.nls_action_button);
+ button.setEnabled(true);
+ }
+
+ @Override
+ boolean autoStart() {
+ return true;
+ }
+
+ @Override
+ void test() {
+ NotificationChannelGroup group = mNm.getNotificationChannelGroup(mGroupId);
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+
+ if (group.isBlocked()) {
+ if (prefs.contains(mGroupId) && prefs.getBoolean(mGroupId, false)) {
+ status = PASS;
+ } else {
+ if (mRetries > 0) {
+ mRetries--;
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
+ }
+ } else {
+ // user hasn't jumped to settings to block the channel yet
+ status = WAIT_FOR_USER;
+ }
+
+ next();
+ }
+
+ void tearDown() {
+ MockListener.getInstance().resetData();
+ mNm.deleteNotificationChannelGroup(mGroupId);
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ NotificationListenerVerifierActivity.PREFS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(mGroupId);
+ }
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(Settings.ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
+ .putExtra(EXTRA_CHANNEL_GROUP_ID, mGroupId);
+ }
+ }
+
private class DataIntactTest extends InteractiveTestCase {
@Override
View inflate(ViewGroup parent) {
@@ -265,9 +442,6 @@
"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;
@@ -275,7 +449,7 @@
}
}
- pass &= found.size() == 3;
+ pass &= found.size() >= 3;
status = pass ? PASS : FAIL;
}
@@ -389,6 +563,61 @@
}
}
+ private class DismissOneWithStatsTest extends InteractiveTestCase {
+ int mRetries = 3;
+
+ @Override
+ View inflate(ViewGroup parent) {
+ return createAutoItem(parent, R.string.nls_clear_one_stats);
+ }
+
+ @Override
+ void setUp() {
+ createChannel();
+ sendNotifications();
+ status = READY;
+ }
+
+ @Override
+ void test() {
+ if (status == READY) {
+ MockListener.getInstance().cancelNotification(
+ MockListener.getInstance().getKeyForTag(mTag1));
+ status = RETEST;
+ } else {
+ List<JSONObject> result =
+ new ArrayList<>(MockListener.getInstance().mRemovedReason.values());
+ boolean pass = true;
+ for (JSONObject payload : result) {
+ try {
+ pass &= (payload.getBoolean(JSON_STATS) == false);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ pass = false;
+ }
+ }
+ if (pass) {
+ status = PASS;
+ } else {
+ if (--mRetries > 0) {
+ sleep(100);
+ status = RETEST;
+ } else {
+ logFail("Notification listener got populated stats object.");
+ status = FAIL;
+ }
+ }
+ }
+ }
+
+ @Override
+ void tearDown() {
+ mNm.cancelAll();
+ deleteChannel();
+ MockListener.getInstance().resetData();
+ }
+ }
+
private class DismissAllTest extends InteractiveTestCase {
@Override
View inflate(ViewGroup parent) {
@@ -458,6 +687,7 @@
}
private class ServiceStoppedTest extends InteractiveTestCase {
+ int mRetries = 3;
@Override
View inflate(ViewGroup parent) {
return createAutoItem(parent, R.string.nls_service_stopped);
@@ -465,12 +695,23 @@
@Override
void test() {
- if (mNm.getEffectsSuppressor() == null) {
+ if (mNm.getEffectsSuppressor() == null && (MockListener.getInstance() == null
+ || !MockListener.getInstance().isConnected)) {
status = PASS;
} else {
- status = FAIL;
+ if (--mRetries > 0) {
+ sleep(100);
+ status = RETEST;
+ } else {
+ status = FAIL;
+ }
}
}
+
+ @Override
+ protected Intent getIntent() {
+ return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+ }
}
private class NotificationNotReceivedTest extends InteractiveTestCase {
@@ -489,12 +730,16 @@
@Override
void test() {
- List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
- if (result.size() == 0) {
+ if (MockListener.getInstance() == null) {
status = PASS;
} else {
- logFail();
- status = FAIL;
+ List<String> result = new ArrayList<>(MockListener.getInstance().mPosted);
+ if (result.size() == 0) {
+ status = PASS;
+ } else {
+ logFail();
+ status = FAIL;
+ }
}
next();
}
@@ -503,7 +748,9 @@
void tearDown() {
mNm.cancelAll();
deleteChannel();
- MockListener.getInstance().resetData();
+ if (MockListener.getInstance() != null) {
+ MockListener.getInstance().resetData();
+ }
}
}
diff --git a/apps/PermissionApp/AndroidManifest.xml b/apps/PermissionApp/AndroidManifest.xml
index 82e3617..f880933 100644
--- a/apps/PermissionApp/AndroidManifest.xml
+++ b/apps/PermissionApp/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-sdk android:minSdkVersion="23"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<application android:label="CtsPermissionApp"
android:icon="@drawable/ic_permissionapp">
diff --git a/build/device_info_package.mk b/build/device_info_package.mk
index 5c290b0..7922f28 100644
--- a/build/device_info_package.mk
+++ b/build/device_info_package.mk
@@ -19,7 +19,9 @@
DEVICE_INFO_PACKAGE := com.android.compatibility.common.deviceinfo
DEVICE_INFO_INSTRUMENT := android.support.test.runner.AndroidJUnitRunner
DEVICE_INFO_USES_LIBRARY := android.test.runner
-DEVICE_INFO_PERMISSIONS += android.permission.WRITE_EXTERNAL_STORAGE
+DEVICE_INFO_PERMISSIONS += \
+ android.permission.READ_PHONE_STATE \
+ android.permission.WRITE_EXTERNAL_STORAGE
DEVICE_INFO_ACTIVITIES += \
$(DEVICE_INFO_PACKAGE).ConfigurationDeviceInfo \
$(DEVICE_INFO_PACKAGE).CpuDeviceInfo \
@@ -42,7 +44,7 @@
endif
ifeq ($(DEVICE_INFO_TARGET_SDK),)
-DEVICE_INFO_TARGET_SDK := 8
+DEVICE_INFO_TARGET_SDK := 17
endif
# Add the base device info
diff --git a/common/device-side/device-info/Android.mk b/common/device-side/device-info/Android.mk
index bf6e122..4ab1ac6 100644
--- a/common/device-side/device-info/Android.mk
+++ b/common/device-side/device-info/Android.mk
@@ -23,8 +23,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
android-support-test \
- junit \
- legacy-android-test
+ junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_MODULE := compatibility-device-info
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
index cb4324c..c81f8fe 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
@@ -73,7 +73,7 @@
store.addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
store.addResult(BUILD_ABI, Build.CPU_ABI);
store.addResult(BUILD_ABI2, Build.CPU_ABI2);
- store.addResult(BUILD_SERIAL, Build.SERIAL);
+ store.addResult(BUILD_SERIAL, Build.getSerial());
store.addResult(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
store.addResult(BUILD_VERSION_SDK, Build.VERSION.SDK);
store.addResult(BUILD_REFERENCE_FINGERPRINT,
diff --git a/common/device-side/device-info/tests/Android.mk b/common/device-side/device-info/tests/Android.mk
index d40614c..db4f250 100644
--- a/common/device-side/device-info/tests/Android.mk
+++ b/common/device-side/device-info/tests/Android.mk
@@ -18,9 +18,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-info junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-info junit
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_MODULE_TAGS := optional
diff --git a/common/device-side/nativetesthelper/Android.mk b/common/device-side/nativetesthelper/Android.mk
new file mode 100644
index 0000000..19f584d
--- /dev/null
+++ b/common/device-side/nativetesthelper/Android.mk
@@ -0,0 +1,25 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-common-util-devicesidelib
+LOCAL_MODULE := nativetesthelper
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/device-side/nativetesthelper/jni/Android.mk b/common/device-side/nativetesthelper/jni/Android.mk
new file mode 100644
index 0000000..f970e8c
--- /dev/null
+++ b/common/device-side/nativetesthelper/jni/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# This is the shared library included by the JNI test app.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativetesthelper_jni
+
+LOCAL_SRC_FILES := \
+ gtest_wrapper.cpp
+
+LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++
+LOCAL_WHOLE_STATIC_LIBRARIES := libgtest_ndk_c++
+LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := libgtest_ndk_c++
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_MULTILIB := both
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/common/device-side/nativetesthelper/jni/gtest_wrapper.cpp b/common/device-side/nativetesthelper/jni/gtest_wrapper.cpp
new file mode 100644
index 0000000..1f91b3a
--- /dev/null
+++ b/common/device-side/nativetesthelper/jni/gtest_wrapper.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <gtest/gtest.h>
+
+static struct {
+ jclass clazz;
+
+ /** static methods **/
+ jmethodID createTestDescription;
+
+ /** methods **/
+ jmethodID addChild;
+} gDescription;
+
+static struct {
+ jclass clazz;
+
+ jmethodID fireTestStarted;
+ jmethodID fireTestIgnored;
+ jmethodID fireTestFailure;
+ jmethodID fireTestFinished;
+
+} gRunNotifier;
+
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gAssertionFailure;
+
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gFailure;
+
+jobject gEmptyAnnotationsArray;
+
+static jobject createTestDescription(JNIEnv* env, const char* className, const char* testName) {
+ ScopedLocalRef<jstring> jClassName(env, env->NewStringUTF(className));
+ ScopedLocalRef<jstring> jTestName(env, env->NewStringUTF(testName));
+ return env->CallStaticObjectMethod(gDescription.clazz, gDescription.createTestDescription,
+ jClassName.get(), jTestName.get(), gEmptyAnnotationsArray);
+}
+
+static void addChild(JNIEnv* env, jobject description, jobject childDescription) {
+ env->CallVoidMethod(description, gDescription.addChild, childDescription);
+}
+
+
+class JUnitNotifyingListener : public ::testing::EmptyTestEventListener {
+public:
+
+ JUnitNotifyingListener(JNIEnv* env, jobject runNotifier)
+ : mEnv(env)
+ , mRunNotifier(runNotifier)
+ , mCurrentTestDescription{env, nullptr}
+ {}
+ virtual ~JUnitNotifyingListener() {}
+
+ virtual void OnTestStart(const testing::TestInfo &testInfo) override {
+ mCurrentTestDescription.reset(
+ createTestDescription(mEnv, testInfo.test_case_name(), testInfo.name()));
+ notify(gRunNotifier.fireTestStarted);
+ }
+
+ virtual void OnTestPartResult(const testing::TestPartResult &testPartResult) override {
+ if (!testPartResult.passed()) {
+ char message[1024];
+ snprintf(message, 1024, "%s:%d\n%s", testPartResult.file_name(), testPartResult.line_number(),
+ testPartResult.message());
+ ScopedLocalRef<jstring> jmessage(mEnv, mEnv->NewStringUTF(message));
+ ScopedLocalRef<jobject> jthrowable(mEnv, mEnv->NewObject(gAssertionFailure.clazz,
+ gAssertionFailure.ctor, jmessage.get()));
+ ScopedLocalRef<jobject> jfailure(mEnv, mEnv->NewObject(gFailure.clazz,
+ gFailure.ctor, mCurrentTestDescription.get(), jthrowable.get()));
+ mEnv->CallVoidMethod(mRunNotifier, gRunNotifier.fireTestFailure, jfailure.get());
+ }
+ }
+
+ virtual void OnTestEnd(const testing::TestInfo&) override {
+ notify(gRunNotifier.fireTestFinished);
+ mCurrentTestDescription.reset();
+ }
+
+ virtual void OnTestProgramEnd(const testing::UnitTest& unitTest) override {
+ // Invoke the notifiers for all the disabled tests
+ for (int testCaseIndex = 0; testCaseIndex < unitTest.total_test_case_count(); testCaseIndex++) {
+ auto testCase = unitTest.GetTestCase(testCaseIndex);
+ for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
+ auto testInfo = testCase->GetTestInfo(testIndex);
+ if (!testInfo->should_run()) {
+ mCurrentTestDescription.reset(
+ createTestDescription(mEnv, testCase->name(), testInfo->name()));
+ notify(gRunNotifier.fireTestIgnored);
+ mCurrentTestDescription.reset();
+ }
+ }
+ }
+ }
+
+private:
+ void notify(jmethodID method) {
+ mEnv->CallVoidMethod(mRunNotifier, method, mCurrentTestDescription.get());
+ }
+
+ JNIEnv* mEnv;
+ jobject mRunNotifier;
+ ScopedLocalRef<jobject> mCurrentTestDescription;
+};
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_android_gtestrunner_GtestRunner_nInitialize(JNIEnv *env, jclass, jobject suite) {
+ // Initialize gtest, removing the default result printer
+ int argc = 1;
+ const char* argv[] = { "gtest_wrapper" };
+ ::testing::InitGoogleTest(&argc, (char**) argv);
+
+ auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
+ delete listeners.Release(listeners.default_result_printer());
+
+ gDescription.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/Description"));
+ gDescription.createTestDescription = env->GetStaticMethodID(gDescription.clazz, "createTestDescription",
+ "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/annotation/Annotation;)Lorg/junit/runner/Description;");
+ gDescription.addChild = env->GetMethodID(gDescription.clazz, "addChild",
+ "(Lorg/junit/runner/Description;)V");
+
+ jclass annotations = env->FindClass("java/lang/annotation/Annotation");
+ gEmptyAnnotationsArray = env->NewGlobalRef(env->NewObjectArray(0, annotations, nullptr));
+
+ gAssertionFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("java/lang/AssertionError"));
+ gAssertionFailure.ctor = env->GetMethodID(gAssertionFailure.clazz, "<init>", "(Ljava/lang/Object;)V");
+
+ gFailure.clazz = (jclass) env->NewGlobalRef(env->FindClass("org/junit/runner/notification/Failure"));
+ gFailure.ctor = env->GetMethodID(gFailure.clazz, "<init>",
+ "(Lorg/junit/runner/Description;Ljava/lang/Throwable;)V");
+
+ gRunNotifier.clazz = (jclass) env->NewGlobalRef(
+ env->FindClass("org/junit/runner/notification/RunNotifier"));
+ gRunNotifier.fireTestStarted = env->GetMethodID(gRunNotifier.clazz, "fireTestStarted",
+ "(Lorg/junit/runner/Description;)V");
+ gRunNotifier.fireTestIgnored = env->GetMethodID(gRunNotifier.clazz, "fireTestIgnored",
+ "(Lorg/junit/runner/Description;)V");
+ gRunNotifier.fireTestFinished = env->GetMethodID(gRunNotifier.clazz, "fireTestFinished",
+ "(Lorg/junit/runner/Description;)V");
+ gRunNotifier.fireTestFailure = env->GetMethodID(gRunNotifier.clazz, "fireTestFailure",
+ "(Lorg/junit/runner/notification/Failure;)V");
+
+ auto unitTest = ::testing::UnitTest::GetInstance();
+ for (int testCaseIndex = 0; testCaseIndex < unitTest->total_test_case_count(); testCaseIndex++) {
+ auto testCase = unitTest->GetTestCase(testCaseIndex);
+ for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
+ auto testInfo = testCase->GetTestInfo(testIndex);
+ ScopedLocalRef<jobject> testDescription(env,
+ createTestDescription(env, testCase->name(), testInfo->name()));
+ addChild(env, suite, testDescription.get());
+ }
+ }
+}
+
+extern "C"
+JNIEXPORT jboolean JNICALL
+Java_com_android_gtestrunner_GtestRunner_nRun(JNIEnv *env, jclass, jobject notifier) {
+ auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
+ JUnitNotifyingListener junitListener{env, notifier};
+ listeners.Append(&junitListener);
+ int success = RUN_ALL_TESTS();
+ listeners.Release(&junitListener);
+ return success == 0;
+}
diff --git a/common/device-side/nativetesthelper/src/com/android/gtestrunner/GtestRunner.java b/common/device-side/nativetesthelper/src/com/android/gtestrunner/GtestRunner.java
new file mode 100644
index 0000000..222a1a0
--- /dev/null
+++ b/common/device-side/nativetesthelper/src/com/android/gtestrunner/GtestRunner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gtestrunner;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+
+public class GtestRunner extends Runner {
+ private static boolean sOnceFlag = false;
+
+ private Class mTargetClass;
+ private Description mDescription;
+
+ public GtestRunner(Class testClass) {
+ synchronized (GtestRunner.class) {
+ if (sOnceFlag) {
+ throw new IllegalStateException("Error multiple GtestRunners defined");
+ }
+ sOnceFlag = true;
+ }
+
+ mTargetClass = testClass;
+ TargetLibrary library = (TargetLibrary) testClass.getAnnotation(TargetLibrary.class);
+ if (library == null) {
+ throw new IllegalStateException("Missing required @TargetLibrary annotation");
+ }
+ System.loadLibrary(library.value());
+ mDescription = Description.createSuiteDescription(testClass);
+ nInitialize(mDescription);
+ }
+
+ @Override
+ public Description getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ nRun(notifier);
+ }
+
+ private static native void nInitialize(Description description);
+ private static native void nRun(RunNotifier notifier);
+}
diff --git a/common/device-side/nativetesthelper/src/com/android/gtestrunner/TargetLibrary.java b/common/device-side/nativetesthelper/src/com/android/gtestrunner/TargetLibrary.java
new file mode 100644
index 0000000..23bc53d
--- /dev/null
+++ b/common/device-side/nativetesthelper/src/com/android/gtestrunner/TargetLibrary.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gtestrunner;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface TargetLibrary {
+ String value();
+}
diff --git a/common/device-side/test-app/AndroidManifest.xml b/common/device-side/test-app/AndroidManifest.xml
index 9c857f0..883b439 100755
--- a/common/device-side/test-app/AndroidManifest.xml
+++ b/common/device-side/test-app/AndroidManifest.xml
@@ -17,7 +17,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.compatibility.common">
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="com.android.compatibility.common.deviceinfo.TestDeviceInfo" />
diff --git a/common/device-side/util/Android.mk b/common/device-side/util/Android.mk
index 26a37ec..fbf0ee4 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -23,10 +23,9 @@
compatibility-common-util-devicesidelib \
android-support-test \
ub-uiautomator \
- mockito-target-minus-junit4 \
- legacy-android-test
+ mockito-target-minus-junit4
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_MODULE_TAGS := optional
diff --git a/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java b/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
index aed5926..b68b34c 100644
--- a/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
+++ b/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
@@ -30,11 +30,11 @@
private static final String PACKAGE = "test.package";
private static final String INSTRUMENT = "test.package.TestInstrument";
private static final String MIN_SDK = "8";
- private static final String TARGET_SDK = "9";
+ private static final String TARGET_SDK = "17";
private static final String MANIFEST = "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>\r\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+ "package=\"test.package\">\r\n"
- + " <uses-sdk android:minSdkVersion=\"8\" android:targetSdkVersion=\"9\" />\r\n"
+ + " <uses-sdk android:minSdkVersion=\"8\" android:targetSdkVersion=\"17\" />\r\n"
+ "%s"
+ " <application>\r\n"
+ "%s"
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index 50d1c3a..48ed864 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -16,6 +16,7 @@
package com.android.compatibility.common.tradefed.build;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.build.IFolderBuildInfo;
import com.android.tradefed.build.VersionedFile;
import com.android.tradefed.util.FileUtil;
@@ -232,18 +233,6 @@
}
/**
- * @return a {@link File} representing the directory to store screenshots taken while testing.
- * @throws FileNotFoundException if the directory structure is not valid.
- */
- public File getScreenshotsDir() throws FileNotFoundException {
- File screenshotsDir = new File(getResultDir(), "screenshots");
- if (!screenshotsDir.exists()) {
- screenshotsDir.mkdirs();
- }
- return screenshotsDir;
- }
-
- /**
* @return a {@link File} representing the test modules directory.
* @throws FileNotFoundException if the directory structure is not valid.
*/
@@ -264,6 +253,12 @@
}
if (testsDir == null) {
+ if (mBuildInfo instanceof IDeviceBuildInfo) {
+ testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir();
+ }
+ }
+
+ if (testsDir == null) {
String altTestsDir = System.getenv().get(ALT_HOST_TESTCASE_DIR);
if (altTestsDir != null) {
testsDir = new File(altTestsDir);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
index 3654d78..ad5e60f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProvider.java
@@ -16,7 +16,6 @@
package com.android.compatibility.common.tradefed.build;
import com.android.annotations.VisibleForTesting;
-import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.DeviceBuildInfo;
import com.android.tradefed.build.IBuildInfo;
@@ -29,6 +28,8 @@
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
import com.android.tradefed.testtype.suite.TestSuiteInfo;
import com.android.tradefed.util.FileUtil;
@@ -42,7 +43,7 @@
* A simple {@link IBuildProvider} that uses a pre-existing Compatibility install.
*/
@OptionClass(alias="compatibility-build-provider")
-public class CompatibilityBuildProvider implements IDeviceBuildProvider {
+public class CompatibilityBuildProvider implements IDeviceBuildProvider, IInvocationContextReceiver {
private static final Pattern RELEASE_BUILD = Pattern.compile("^[A-Z]{3}\\d{2}[A-Z]{0,1}$");
private static final String ROOT_DIR = "ROOT_DIR";
@@ -75,9 +76,6 @@
@Option(name="use-device-build-info", description="Bootstrap build info from device")
private boolean mUseDeviceBuildInfo = false;
- @Option(name="test-tag", description="test tag name to supply.")
- private String mTestTag = "cts";
-
@Option(name = "dynamic-config-url",
description = "Specify the url for override config")
private String mURL = "https://androidpartner.googleapis.com/v1/dynamicconfig/"
@@ -88,6 +86,8 @@
importance = Importance.ALWAYS)
private String mSuitePlan;
+ private String mTestTag;
+
/**
* Util method to inject build attributes into supplied {@link IBuildInfo}
* @param buildInfo
@@ -102,6 +102,14 @@
* {@inheritDoc}
*/
@Override
+ public void setInvocationContext(IInvocationContext invocationContext) {
+ mTestTag = invocationContext.getTestTag();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public IBuildInfo getBuild() {
// Create a blank BuildInfo which will get populated later.
String version = null;
@@ -113,7 +121,7 @@
version = IBuildInfo.UNKNOWN_BUILD_ID;
}
}
- IBuildInfo ctsBuild = new BuildInfo(version, mTestTag);
+ IBuildInfo ctsBuild = new DeviceBuildInfo(version, mTestTag);
if (mBranch != null) {
ctsBuild.setBuildBranch(mBranch);
}
@@ -208,7 +216,7 @@
info.addBuildAttribute(ROOT_DIR, rootDir.getAbsolutePath());
// For DeviceBuildInfo we populate the testsDir folder of the build info.
if (info instanceof IDeviceBuildInfo) {
- File testDir = new File(rootDir, String.format("android-%s/testcases/",
+ File testDir = new File(rootDir, String.format("android-%s/testcases/",
getSuiteInfoName().toLowerCase()));
((IDeviceBuildInfo) info).setTestsDir(testDir, "0");
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 32e6197..fe29c7f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -195,7 +195,12 @@
"\t\t\t--screenshot-on-failure: Capture a screenshot when a test fails"
+ LINE_SEPARATOR +
"\t\t\t--shard-count <shards>: Shards a run into the given number of independent " +
- "chunks, to run on multiple devices in parallel." + LINE_SEPARATOR;
+ "chunks, to run on multiple devices in parallel." + LINE_SEPARATOR +
+ "\t ----- In order to retry a previous run -----" + LINE_SEPARATOR +
+ "\tretry --retry <session id to retry> [--retry-type <FAILED | NOT_EXECUTED>]"
+ + LINE_SEPARATOR +
+ "\t\tWithout --retry-type, retry will run both FAIL and NOT_EXECUTED tests"
+ + LINE_SEPARATOR;
commandHelp.put(RUN_PATTERN, combinedRunHelp);
commandHelp.put(ADD_PATTERN, String.format(
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index 574a2a1..11a7466 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -16,7 +16,8 @@
package com.android.compatibility.common.tradefed.result;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
import com.android.compatibility.common.tradefed.util.RetryType;
import com.android.compatibility.common.util.ChecksumReporter;
import com.android.compatibility.common.util.DeviceInfo;
@@ -39,6 +40,7 @@
import com.android.tradefed.config.OptionCopier;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ILogSaver;
import com.android.tradefed.result.ILogSaverListener;
import com.android.tradefed.result.IShardableListener;
@@ -49,6 +51,7 @@
import com.android.tradefed.result.LogFile;
import com.android.tradefed.result.LogFileSaver;
import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.result.suite.SuiteResultReporter;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.TimeUtil;
@@ -63,6 +66,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -80,10 +85,12 @@
public class ResultReporter implements ILogSaverListener, ITestInvocationListener,
ITestSummaryListener, IShardableListener {
+ public static final String INCLUDE_HTML_IN_ZIP = "html-in-zip";
private static final String UNKNOWN_DEVICE = "unknown_device";
private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
private static final String CTS_PREFIX = "cts:";
private static final String BUILD_INFO = CTS_PREFIX + "build_";
+ private static final String LATEST_LINK_NAME = "latest";
public static final String BUILD_BRAND = "build_brand";
public static final String BUILD_DEVICE = "build_device";
@@ -100,14 +107,14 @@
ResultHandler.FAILURE_REPORT_NAME,
"diffs");
- @Option(name = CompatibilityTest.RETRY_OPTION,
+ @Option(name = RetryFactoryTest.RETRY_OPTION,
shortName = 'r',
description = "retry a previous session.",
importance = Importance.IF_UNSET)
private Integer mRetrySessionId = null;
- @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
- description = "used with " + CompatibilityTest.RETRY_OPTION
+ @Option(name = RetryFactoryTest.RETRY_TYPE_OPTION,
+ description = "used with " + RetryFactoryTest.RETRY_OPTION
+ ", retry tests of a certain status. Possible values include \"failed\", "
+ "\"not_executed\", and \"custom\".",
importance = Importance.IF_UNSET)
@@ -128,6 +135,10 @@
@Option(name = "compress-logs", description = "Whether logs will be saved with compression")
private boolean mCompressLogs = true;
+ @Option(name = INCLUDE_HTML_IN_ZIP,
+ description = "Whether failure summary report is included in the zip fie.")
+ private boolean mIncludeHtml = false;
+
private CompatibilityBuildHelper mBuildHelper;
private File mResultDir = null;
private File mLogDir = null;
@@ -449,10 +460,24 @@
*/
@Override
public void putSummary(List<TestSummary> summaries) {
- // This is safe to be invoked on either the master or a shard ResultReporter,
- // but the value added to the report will be that of the master ResultReporter.
- if (summaries.size() > 0) {
- mReferenceUrl = summaries.get(0).getSummary().getString();
+ for (TestSummary summary : summaries) {
+ // If one summary is from SuiteResultReporter, log it as an extra file.
+ if (SuiteResultReporter.SUITE_REPORTER_SOURCE.equals(summary.getSource())) {
+ File summaryFile = null;
+ try {
+ summaryFile = FileUtil.createTempFile("summary", ".txt");
+ FileUtil.writeToFile(summary.getSummary().getString(), summaryFile);
+ try (InputStreamSource stream = new FileInputStreamSource(summaryFile)) {
+ testLog("summary", LogDataType.TEXT, stream);
+ }
+ } catch (IOException e) {
+ CLog.e(e);
+ } finally {
+ FileUtil.deleteFile(summaryFile);
+ }
+ } else if (mReferenceUrl == null && summary.getSummary().getString() != null) {
+ mReferenceUrl = summary.getSummary().getString();
+ }
}
}
@@ -521,10 +546,17 @@
copyRetryFiles(ResultHandler.getResultDirectory(
mBuildHelper.getResultsDir(), mRetrySessionId), mResultDir);
}
+ File failureReport = null;
+ if (mIncludeHtml) {
+ // Create the html report before the zip file.
+ failureReport = ResultHandler.createFailureReport(resultFile);
+ }
File zippedResults = zipResults(mResultDir);
- // Create failure report after zip file so extra data is not uploaded
- File failureReport = ResultHandler.createFailureReport(resultFile);
- if (failureReport.exists()) {
+ if (!mIncludeHtml) {
+ // Create failure report after zip file so extra data is not uploaded
+ failureReport = ResultHandler.createFailureReport(resultFile);
+ }
+ if (failureReport != null && failureReport.exists()) {
info("Test Result: %s", failureReport.getCanonicalPath());
} else {
info("Test Result: %s", resultFile.getCanonicalPath());
@@ -532,6 +564,16 @@
info("Test Logs: %s", mLogDir.getCanonicalPath());
debug("Full Result: %s", zippedResults.getCanonicalPath());
+ Path latestLink = createLatestLinkDirectory(mResultDir.toPath());
+ if (latestLink != null) {
+ info("Latest results link: " + latestLink.toAbsolutePath());
+ }
+
+ latestLink = createLatestLinkDirectory(mLogDir.toPath());
+ if (latestLink != null) {
+ info("Latest logs link: " + latestLink.toAbsolutePath());
+ }
+
saveLog(resultFile, zippedResults);
uploadResult(resultFile);
@@ -548,6 +590,30 @@
moduleProgress);
}
+ private Path createLatestLinkDirectory(Path directory) {
+ Path link = null;
+
+ Path parent = directory.getParent();
+
+ if (parent != null) {
+ link = parent.resolve(LATEST_LINK_NAME);
+ try {
+ // if latest already exists, we have to remove it before creating
+ Files.deleteIfExists(link);
+ Files.createSymbolicLink(link, directory);
+ } catch (IOException ioe) {
+ CLog.e("Exception while attempting to create 'latest' link to: [%s]",
+ directory);
+ CLog.e(ioe);
+ return null;
+ } catch (UnsupportedOperationException uoe) {
+ CLog.e("Failed to create 'latest' symbolic link - unsupported operation");
+ return null;
+ }
+ }
+ return link;
+ }
+
/**
* {@inheritDoc}
*/
@@ -572,6 +638,7 @@
// Handle device info file case
testLogDeviceInfo(name, stream);
} else {
+ // Handle default case
try {
File logFile = null;
if (mCompressLogs) {
@@ -803,11 +870,11 @@
}
return !(RetryType.FAILED.equals(mRetryType)
|| RetryType.CUSTOM.equals(mRetryType)
- || args.contains(CompatibilityTest.INCLUDE_FILTER_OPTION)
- || args.contains(CompatibilityTest.EXCLUDE_FILTER_OPTION)
- || args.contains(CompatibilityTest.SUBPLAN_OPTION)
+ || args.contains(CompatibilityTestSuite.INCLUDE_FILTER_OPTION)
+ || args.contains(CompatibilityTestSuite.EXCLUDE_FILTER_OPTION)
+ || args.contains(CompatibilityTestSuite.SUBPLAN_OPTION)
|| args.matches(String.format(".* (-%s|--%s) .*",
- CompatibilityTest.TEST_OPTION_SHORT_NAME, CompatibilityTest.TEST_OPTION)));
+ CompatibilityTestSuite.TEST_OPTION_SHORT_NAME, CompatibilityTestSuite.TEST_OPTION)));
}
/**
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
index 9c87a75..87f2436 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/SubPlanHelper.java
@@ -16,9 +16,9 @@
package com.android.compatibility.common.tradefed.result;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.testtype.ISubPlan;
import com.android.compatibility.common.tradefed.testtype.SubPlan;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
import com.android.compatibility.common.tradefed.util.OptionHelper;
import com.android.compatibility.common.util.ICaseResult;
import com.android.compatibility.common.util.IInvocationResult;
@@ -33,8 +33,8 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
import com.android.tradefed.util.StreamUtil;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
import com.google.common.annotations.VisibleForTesting;
@@ -43,13 +43,13 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -90,32 +90,32 @@
importance=Importance.IF_UNSET)
private Set<String> mResultTypes = new HashSet<String>();
- @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+ @Option(name = CompatibilityTestSuite.INCLUDE_FILTER_OPTION,
description = "the include module filters to apply.",
importance = Importance.NEVER)
private Set<String> mIncludeFilters = new HashSet<String>();
- @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+ @Option(name = CompatibilityTestSuite.EXCLUDE_FILTER_OPTION,
description = "the exclude module filters to apply.",
importance = Importance.NEVER)
private Set<String> mExcludeFilters = new HashSet<String>();
- @Option(name = CompatibilityTest.MODULE_OPTION, shortName = 'm',
+ @Option(name = CompatibilityTestSuite.MODULE_OPTION, shortName = 'm',
description = "the test module to run.",
importance = Importance.NEVER)
private String mModuleName = null;
- @Option(name = CompatibilityTest.TEST_OPTION, shortName = 't',
+ @Option(name = CompatibilityTestSuite.TEST_OPTION, shortName = 't',
description = "the test to run.",
importance = Importance.NEVER)
private String mTestName = null;
- @Option(name = CompatibilityTest.ABI_OPTION, shortName = 'a',
+ @Option(name = CompatibilityTestSuite.ABI_OPTION, shortName = 'a',
description = "the abi to test.",
importance = Importance.NEVER)
private String mAbiName = null;
- @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+ @Option(name = CompatibilityTestSuite.SUBPLAN_OPTION,
description = "the subplan used in the previous session",
importance = Importance.NEVER)
private String mLastSubPlan;
@@ -159,7 +159,7 @@
throw new RuntimeException(
String.format("Unable to find or parse subplan %s", name), e);
} finally {
- StreamUtil.closeStream(subPlanInputStream);
+ StreamUtil.close(subPlanInputStream);
}
}
@@ -207,7 +207,7 @@
* Create a subplan derived from a result.
* <p/>
* {@link Option} values must be set before this is called.
- * @param build
+ * @param buildHelper
* @return subplan
* @throws ConfigurationException
*/
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
index 7ebd717..749be97 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/BusinessLogicHostTestBase.java
@@ -29,13 +29,14 @@
import com.android.compatibility.common.util.BusinessLogicFactory;
import com.android.compatibility.common.util.BusinessLogicHostExecutor;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import java.io.File;
/**
* Host-side base class for tests leveraging the Business Logic service.
*/
-public class BusinessLogicHostTestBase extends CompatibilityHostTestBase {
+public class BusinessLogicHostTestBase extends BaseHostJUnit4Test {
/* String marking the beginning of the parameter in a test name */
private static final String PARAM_START = "[";
@@ -51,7 +52,7 @@
// Business logic must be retrieved in this @Before method, since the build info contains
// the location of the business logic file and cannot be referenced from a static context
if (mBusinessLogic == null) {
- CompatibilityBuildHelper helper = new CompatibilityBuildHelper(mBuild);
+ CompatibilityBuildHelper helper = new CompatibilityBuildHelper(getBuild());
File businessLogicFile = helper.getBusinessLogicHostFile();
if (businessLogicFile != null && businessLogicFile.canRead()) {
mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
@@ -71,7 +72,7 @@
if (mBusinessLogic.hasLogicFor(testName)) {
CLog.i("Applying business logic for test case: ", testName);
BusinessLogicExecutor executor = new BusinessLogicHostExecutor(getDevice(),
- mBuild, this);
+ getBuild(), this);
mBusinessLogic.applyLogicFor(testName, executor);
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
deleted file mode 100644
index 1222a1f..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBase.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.tradefed.testtype;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.testtype.IDeviceTest;
-import com.android.tradefed.util.AbiUtils;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.internal.AssumptionViolatedException;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Compatibility host test base class for JUnit4 tests. Enables host-side tests written in JUnit4
- * to access build and ABI information, as well as a reference to the testing device. The class
- * includes methods to install and uninstall test packages, as well as methods to run device-side
- * tests and retrieve their results.
- */
-public class CompatibilityHostTestBase implements IAbiReceiver, IBuildReceiver, IDeviceTest {
-
- protected static final String AJUR = "android.support.test.runner.AndroidJUnitRunner";
-
- /** The build will be used. */
- protected IBuildInfo mBuild;
-
- /** The ABI to use. */
- protected IAbi mAbi;
-
- /** A reference to the device under test. */
- protected ITestDevice mDevice;
-
- /** The test runner used for test apps */
- private String mRunner;
-
- @Override
- public void setAbi(IAbi abi) {
- mAbi = abi;
- }
-
- @Override
- public IAbi getAbi() {
- return mAbi;
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- // Get the build, this is used to access the APK.
- mBuild = buildInfo;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public ITestDevice getDevice() {
- return mDevice;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setDevice(ITestDevice device) {
- mDevice = device;
- }
-
- @Before
- public void baseSetUp() throws Exception {
- mRunner = AJUR;
- }
-
- /**
- * Set the runner name
- * @param runner of the device test runner
- */
- protected void setRunner(String runner) {
- mRunner = runner;
- }
-
- /**
- * Get the runner name
- * @return name of the device test runner
- */
- protected String getRunner() {
- return mRunner;
- }
-
- /**
- * Installs a package on the device
- * @param fileName the name of the file to install
- * @param options optional extra arguments to pass. See 'adb shell pm install --help' for
- * available options
- * @throws FileNotFoundException if file with filename cannot be found
- * @throws DeviceNotAvailableException
- */
- protected void installPackage(String fileName, String... options)
- throws FileNotFoundException, DeviceNotAvailableException {
-
- final List<String> optList = new ArrayList<>(Arrays.asList(options));
- optList.add(AbiUtils.createAbiFlag(mAbi.getName()));
- options = optList.toArray(new String[optList.size()]);
-
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
- File testFile = buildHelper.getTestFile(fileName);
- // Install the APK on the device.
- String installResult = mDevice.installPackage(testFile, true, options);
-
- assertNull(String.format("Failed to install %s, Reason: %s", fileName, installResult),
- installResult);
- }
-
- /**
- * Uninstalls a package on the device
- * @param pkgName the Android package to uninstall
- * @return a {@link String} with an error code, or <code>null</code> if success
- * @throws DeviceNotAvailableException
- */
- protected String uninstallPackage(String pkgName) throws DeviceNotAvailableException {
- return mDevice.uninstallPackage(pkgName);
- }
-
- /**
- * Checks if a package of a given name is installed on the device
- * @param pkg the name of the package
- * @return true if the package is found on the device
- * @throws DeviceNotAvailableException
- */
- protected boolean isPackageInstalled(String pkg) throws DeviceNotAvailableException {
- for (String installedPackage : mDevice.getInstalledPackageNames()) {
- if (pkg.equals(installedPackage)) {
- return true;
- }
- }
- return false;
- }
-
- private void printTestResult(TestRunResult runResult) {
- for (Map.Entry<TestIdentifier, TestResult> testEntry :
- runResult.getTestResults().entrySet()) {
- TestResult testResult = testEntry.getValue();
- TestStatus testStatus = testResult.getStatus();
- CLog.logAndDisplay(LogLevel.INFO,
- "Test " + testEntry.getKey() + ": " + testStatus);
- if (testStatus != TestStatus.PASSED && testStatus != TestStatus.ASSUMPTION_FAILURE) {
- CLog.logAndDisplay(LogLevel.WARN, testResult.getStackTrace());
- }
- }
- }
-
- /**
- * Runs tests of a given package on the device and reports success.
- * @param pkgName the name of the package containing tests
- * @param testClassName the class from which tests should be collected. Tests are collected
- * from all test classes in the package if null
- * @return true if at least once test runs and there are no failures
- * @throws AssertionError if device fails to run instrumentation tests
- * @throws AssumptionViolatedException if each device test fails an assumption
- * @throws DeviceNotAvailableException
- */
- protected boolean runDeviceTests(String pkgName, @Nullable String testClassName)
- throws DeviceNotAvailableException {
- return runDeviceTests(pkgName, testClassName, null /*testMethodName*/);
- }
-
- /**
- * Runs tests of a given package on the device and reports success.
- * @param pkgName the name of the package containing tests
- * @param testClassName the class from which tests should be collected. Tests are collected
- * from all test classes in the package if null
- * @param testMethodName the test method to run. All tests from the class or package are run
- * if null
- * @return true if at least once test runs and there are no failures
- * @throws AssertionError if device fails to run instrumentation tests
- * @throws AssumptionViolatedException if each device test fails an assumption
- * @throws DeviceNotAvailableException
- */
- protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
- @Nullable String testMethodName)
- throws DeviceNotAvailableException {
- TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName);
- printTestResult(runResult);
- // assume not all tests have skipped (and rethrow AssumptionViolatedException if so)
- Assume.assumeTrue(runResult.getNumTests() != runResult.getNumTestsInState(
- TestStatus.ASSUMPTION_FAILURE));
- return !runResult.hasFailedTests() && runResult.getNumTests() > 0;
- }
-
- /** Helper method to run tests and return the listener that collected the results. */
- private TestRunResult doRunTests(
- String pkgName, String testClassName,
- String testMethodName) throws DeviceNotAvailableException {
- RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
- pkgName, mRunner, mDevice.getIDevice());
- if (testClassName != null && testMethodName != null) {
- testRunner.setMethodName(testClassName, testMethodName);
- } else if (testClassName != null) {
- testRunner.setClassName(testClassName);
- }
-
- CollectingTestListener listener = createCollectingListener();
- assertTrue(mDevice.runInstrumentationTests(testRunner, listener));
- return listener.getCurrentRunResults();
- }
-
- @VisibleForTesting
- protected CollectingTestListener createCollectingListener() {
- return new CollectingTestListener();
- }
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 07bfe83..a8c016c 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -20,6 +20,7 @@
import com.android.compatibility.common.tradefed.result.InvocationFailureHandler;
import com.android.compatibility.common.tradefed.result.SubPlanHelper;
import com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker;
+import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
import com.android.compatibility.common.tradefed.util.RetryType;
import com.android.compatibility.common.tradefed.util.UniqueModuleCountUtil;
@@ -79,8 +80,10 @@
import java.util.concurrent.TimeUnit;
/**
- * A Test for running Compatibility Suites
+ * A Test for running Compatibility Suites.
+ * @deprecated use {@link CompatibilityTestSuite} instead.
*/
+@Deprecated
@OptionClass(alias = "compatibility")
public class CompatibilityTest implements IDeviceTest, IShardableTest, IBuildReceiver,
IStrictShardableTest, ISystemStatusCheckerReceiver, ITestCollector,
@@ -473,7 +476,16 @@
moduleContext.addInvocationAttribute(IModuleDef.MODULE_ABI,
module.getAbi().getName());
mInvocationContext.setModuleInvocationContext(moduleContext);
+ // Populate the module context with devices and builds
+ for (String deviceName : mInvocationContext.getDeviceConfigNames()) {
+ moduleContext.addAllocatedDevice(
+ deviceName, mInvocationContext.getDevice(deviceName));
+ moduleContext.addDeviceBuildInfo(
+ deviceName, mInvocationContext.getBuildInfo(deviceName));
+ }
+ module.setInvocationContext(moduleContext);
try {
+ listener.testModuleStarted(moduleContext);
module.run(listener);
} catch (DeviceUnresponsiveException due) {
// being able to catch a DeviceUnresponsiveException here implies that recovery
@@ -490,6 +502,7 @@
// clear out module invocation context since we are now done with module
// execution
mInvocationContext.setModuleInvocationContext(null);
+ listener.testModuleEnded();
}
long duration = System.currentTimeMillis() - start;
long expected = module.getRuntimeHint();
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index 7a2c44d..51b33a1 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -21,6 +21,7 @@
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IRuntimeHintProvider;
import com.android.tradefed.testtype.ITestCollector;
@@ -33,7 +34,7 @@
* Container for Compatibility test info.
*/
public interface IModuleDef extends Comparable<IModuleDef>, IBuildReceiver, IDeviceTest,
- IRemoteTest, IRuntimeHintProvider, ITestCollector {
+ IRemoteTest, IRuntimeHintProvider, ITestCollector, IInvocationContextReceiver {
/** key names used for saving module info into {@link IInvocationContext} */
// This currently references ModuleDefinition so that there's only once source for String
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
index 5ef3a93..4104202 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/JarHostTest.java
@@ -36,8 +36,8 @@
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -136,11 +136,25 @@
*/
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
- int numTests = countTestCases();
+ int numTests = 0;
+ RuntimeException bufferedException = null;
+ try {
+ numTests = countTestCases();
+ } catch (RuntimeException e) {
+ bufferedException = e;
+ }
long startTime = System.currentTimeMillis();
listener.testRunStarted(getClass().getName(), numTests);
- super.run(new HostTestListener(listener));
- listener.testRunEnded(System.currentTimeMillis() - startTime, Collections.emptyMap());
+ HostTestListener hostListener = new HostTestListener(listener);
+ try {
+ if (bufferedException != null) {
+ throw bufferedException;
+ }
+ super.run(hostListener);
+ } finally {
+ listener.testRunEnded(System.currentTimeMillis() - startTime,
+ hostListener.getMetrics());
+ }
}
/**
@@ -150,6 +164,8 @@
*/
public class HostTestListener extends ResultForwarder {
+ private Map<String, String> mCollectedMetrics = new HashMap<>();
+
public HostTestListener(ITestInvocationListener listener) {
super(listener);
}
@@ -168,6 +184,14 @@
@Override
public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
CLog.d("HostTestListener.testRunEnded(%d, %s)", elapsedTime, metrics.toString());
+ mCollectedMetrics.putAll(metrics);
+ }
+
+ /**
+ * Returns all the metrics reported by the tests
+ */
+ Map<String, String> getMetrics() {
+ return mCollectedMetrics;
}
}
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index f51be93..ffb7ef7 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -26,6 +26,7 @@
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ResultForwarder;
@@ -37,6 +38,7 @@
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IInvocationContextReceiver;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.IRuntimeHintProvider;
import com.android.tradefed.testtype.ITestCollector;
@@ -68,6 +70,7 @@
private ITestDevice mDevice;
private Set<String> mPreparerWhitelist = new HashSet<>();
private ConfigurationDescriptor mConfigurationDescriptor;
+ private IInvocationContext mContext;
public ModuleDef(String name, IAbi abi, IRemoteTest test,
List<ITargetPreparer> preparers, ConfigurationDescriptor configurationDescriptor) {
@@ -302,6 +305,9 @@
if (mTest instanceof IDeviceTest) {
((IDeviceTest) mTest).setDevice(mDevice);
}
+ if (mTest instanceof IInvocationContextReceiver) {
+ ((IInvocationContextReceiver) mTest).setInvocationContext(mContext);
+ }
}
/**
@@ -409,4 +415,16 @@
public ConfigurationDescriptor getConfigurationDescriptor() {
return mConfigurationDescriptor;
}
+
+ /**
+ * @return the {@link IInvocationContext} for the module
+ */
+ protected IInvocationContext getInvocationContext() {
+ return mContext;
+ }
+
+ @Override
+ public void setInvocationContext(IInvocationContext invocationContext) {
+ mContext = invocationContext;
+ }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
index 40bf633..a95ebfb 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTest.java
@@ -16,7 +16,6 @@
package com.android.compatibility.common.tradefed.testtype.retry;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
import com.android.compatibility.common.tradefed.util.RetryType;
@@ -41,13 +40,14 @@
import com.google.common.annotations.VisibleForTesting;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
- * Runner that creates a {@link CompatibilityTest} to re-run some previous results.
+ * Runner that creates a {@link CompatibilityTestSuite} to re-run some previous results.
* Only the 'cts' plan is supported.
* TODO: explore other new way to build the retry (instead of relying on one massive pair of
* include/exclude filters)
@@ -57,50 +57,58 @@
ISystemStatusCheckerReceiver, IInvocationContextReceiver, IShardableTest {
/**
- * Mirror the {@link CompatibilityTest} options in order to create it.
+ * Mirror the {@link CompatibilityTestSuite} options in order to create it.
*/
public static final String RETRY_OPTION = "retry";
+ public static final String RETRY_TYPE_OPTION = "retry-type";
+
@Option(name = RETRY_OPTION,
shortName = 'r',
description = "retry a previous session's failed and not executed tests.",
mandatory = true)
private Integer mRetrySessionId = null;
- @Option(name = CompatibilityTest.SUBPLAN_OPTION,
+ @Option(name = CompatibilityTestSuite.SUBPLAN_OPTION,
description = "the subplan to run",
importance = Importance.IF_UNSET)
protected String mSubPlan;
- @Option(name = CompatibilityTest.INCLUDE_FILTER_OPTION,
+ @Option(name = CompatibilityTestSuite.INCLUDE_FILTER_OPTION,
description = "the include module filters to apply.",
importance = Importance.ALWAYS)
protected Set<String> mIncludeFilters = new HashSet<>();
- @Option(name = CompatibilityTest.EXCLUDE_FILTER_OPTION,
+ @Option(name = CompatibilityTestSuite.EXCLUDE_FILTER_OPTION,
description = "the exclude module filters to apply.",
importance = Importance.ALWAYS)
protected Set<String> mExcludeFilters = new HashSet<>();
- @Option(name = CompatibilityTest.ABI_OPTION,
+ @Option(name = CompatibilityTestSuite.ABI_OPTION,
shortName = 'a',
description = "the abi to test.",
importance = Importance.IF_UNSET)
protected String mAbiName = null;
- @Option(name = CompatibilityTest.MODULE_OPTION,
+ @Option(name = CompatibilityTestSuite.MODULE_OPTION,
shortName = 'm',
description = "the test module to run.",
importance = Importance.IF_UNSET)
protected String mModuleName = null;
- @Option(name = CompatibilityTest.TEST_OPTION,
- shortName = CompatibilityTest.TEST_OPTION_SHORT_NAME,
+ @Option(name = CompatibilityTestSuite.TEST_OPTION,
+ shortName = CompatibilityTestSuite.TEST_OPTION_SHORT_NAME,
description = "the test run.",
importance = Importance.IF_UNSET)
protected String mTestName = null;
- @Option(name = CompatibilityTest.RETRY_TYPE_OPTION,
- description = "used with " + CompatibilityTest.RETRY_OPTION + ", retry tests"
+ @Option(name = CompatibilityTestSuite.TEST_ARG_OPTION,
+ description = "the arguments to pass to a test. The expected format is "
+ + "\"<test-class>:<arg-name>:[<arg-key>:=]<arg-value>\"",
+ importance = Importance.ALWAYS)
+ private List<String> mTestArgs = new ArrayList<>();
+
+ @Option(name = RETRY_TYPE_OPTION,
+ description = "used with " + RETRY_OPTION + ", retry tests"
+ " of a certain status. Possible values include \"failed\" and \"not_executed\".",
importance = Importance.IF_UNSET)
protected RetryType mRetryType = null;
@@ -176,11 +184,9 @@
try {
OptionSetter setter = new OptionSetter(test);
- setter.setOptionValue("compatibility:test-arg",
- "com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true");
- setter.setOptionValue("compatibility:test-arg",
- "com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:"
- + "false");
+ for (String testArg : mTestArgs) {
+ setter.setOptionValue("compatibility:test-arg", testArg);
+ }
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
@@ -191,13 +197,17 @@
test.setBuild(mBuildInfo);
test.setSystemStatusChecker(mStatusCheckers);
test.setInvocationContext(mContext);
+ // reset the retry id - Ensure that retry of retry does not throw
+ test.resetRetryId();
// clean the helper
helper.tearDown();
return test;
}
- @VisibleForTesting
- RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
+ /**
+ * @return a {@link RetryFilterHelper} created from the attributes of this object.
+ */
+ protected RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
return new RetryFilterHelper(buildHelper, mRetrySessionId, mSubPlan, mIncludeFilters,
mExcludeFilters, mAbiName, mModuleName, mTestName, mRetryType);
}
@@ -206,4 +216,11 @@
CompatibilityTestSuite createTest() {
return new CompatibilityTestSuite();
}
+
+ /**
+ * @return the ID of the session to be retried.
+ */
+ protected Integer getRetrySessionId() {
+ return mRetrySessionId;
+ }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
index bb74f23..80758fd 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
@@ -16,9 +16,9 @@
package com.android.compatibility.common.tradefed.testtype.suite;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.testtype.ISubPlan;
import com.android.compatibility.common.tradefed.testtype.SubPlan;
+import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest;
import com.android.compatibility.common.util.TestFilter;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.IConfiguration;
@@ -26,16 +26,9 @@
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.Abi;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.suite.ITestSuite;
-import com.android.tradefed.testtype.suite.TestSuiteInfo;
-import com.android.tradefed.util.AbiFormatter;
-import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.ArrayUtil;
-import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
import java.io.File;
@@ -43,10 +36,8 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -56,20 +47,17 @@
@OptionClass(alias = "compatibility")
public class CompatibilityTestSuite extends ITestSuite {
- private static final String INCLUDE_FILTER_OPTION = "include-filter";
- private static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
- private static final String SUBPLAN_OPTION = "subplan";
- private static final String MODULE_OPTION = "module";
- private static final String TEST_OPTION = "test";
+ public static final String INCLUDE_FILTER_OPTION = "include-filter";
+ public static final String EXCLUDE_FILTER_OPTION = "exclude-filter";
+ public static final String SUBPLAN_OPTION = "subplan";
+ public static final String MODULE_OPTION = "module";
+ public static final String TEST_ARG_OPTION = "test-arg";
+ public static final String TEST_OPTION = "test";
+ public static final char TEST_OPTION_SHORT_NAME = 't';
private static final String MODULE_ARG_OPTION = "module-arg";
- private static final String TEST_ARG_OPTION = "test-arg";
- private static final String ABI_OPTION = "abi";
- private static final String SKIP_HOST_ARCH_CHECK = "skip-host-arch-check";
- private static final String PRIMARY_ABI_RUN = "primary-abi-only";
- private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi";
// TODO: remove this option when CompatibilityTest goes away
- @Option(name = CompatibilityTest.RETRY_OPTION,
+ @Option(name = RetryFactoryTest.RETRY_OPTION,
shortName = 'r',
description = "Copy of --retry from CompatibilityTest to prevent using it.")
private Integer mRetrySessionId = null;
@@ -96,7 +84,7 @@
private String mModuleName = null;
@Option(name = TEST_OPTION,
- shortName = 't',
+ shortName = TEST_OPTION_SHORT_NAME,
description = "the test to run.",
importance = Importance.IF_UNSET)
private String mTestName = null;
@@ -113,38 +101,6 @@
importance = Importance.ALWAYS)
private List<String> mTestArgs = new ArrayList<>();
- @Option(name = ABI_OPTION,
- shortName = 'a',
- description = "the abi to test.",
- importance = Importance.IF_UNSET)
- private String mAbiName = null;
-
- @Option(name = SKIP_HOST_ARCH_CHECK,
- description = "Whether host architecture check should be skipped")
- private boolean mSkipHostArchCheck = false;
-
- @Option(name = PRIMARY_ABI_RUN,
- description = "Whether to run tests with only the device primary abi. "
- + "This override the --abi option.")
- private boolean mPrimaryAbiRun = false;
-
- @Option(name = "module-metadata-include-filter",
- description = "Include modules for execution based on matching of metadata fields: "
- + "for any of the specified filter name and value, if a module has a metadata "
- + "field with the same name and value, it will be included. When both module "
- + "inclusion and exclusion rules are applied, inclusion rules will be "
- + "evaluated first. Using this together with test filter inclusion rules may "
- + "result in no tests to execute if the rules don't overlap.")
- private MultiMap<String, String> mModuleMetadataIncludeFilter = new MultiMap<>();
-
- @Option(name = "module-metadata-exclude-filter",
- description = "Exclude modules for execution based on matching of metadata fields: "
- + "for any of the specified filter name and value, if a module has a metadata "
- + "field with the same name and value, it will be excluded. When both module "
- + "inclusion and exclusion rules are applied, inclusion rules will be "
- + "evaluated first.")
- private MultiMap<String, String> mModuleMetadataExcludeFilter = new MultiMap<>();
-
private ModuleRepoSuite mModuleRepo = new ModuleRepoSuite();
private CompatibilityBuildHelper mBuildHelper;
@@ -163,8 +119,7 @@
// Initialize the repository, {@link CompatibilityBuildHelper#getTestsDir} can
// throw a {@link FileNotFoundException}
return mModuleRepo.loadConfigs(mBuildHelper.getTestsDir(),
- abis, mTestArgs, mModuleArgs, mIncludeFilters,
- mExcludeFilters, mModuleMetadataIncludeFilter, mModuleMetadataExcludeFilter);
+ abis, mTestArgs, mModuleArgs, mIncludeFilters, mExcludeFilters);
} catch (DeviceNotAvailableException | FileNotFoundException e) {
throw new RuntimeException(e);
}
@@ -180,64 +135,6 @@
}
/**
- * Gets the set of ABIs supported by both Compatibility and the device under test
- *
- * @return The set of ABIs to run the tests on
- * @throws DeviceNotAvailableException
- */
- Set<IAbi> getAbis(ITestDevice device) throws DeviceNotAvailableException {
- Set<IAbi> abis = new LinkedHashSet<>();
- Set<String> archAbis = getAbisForBuildTargetArch();
- if (mPrimaryAbiRun) {
- if (mAbiName == null) {
- // Get the primary from the device and make it the --abi to run.
- mAbiName = device.getProperty(PRODUCT_CPU_ABI_KEY).trim();
- } else {
- CLog.d("Option --%s supersedes the option --%s, using abi: %s", ABI_OPTION,
- PRIMARY_ABI_RUN, mAbiName);
- }
- }
- if (mAbiName != null) {
- // A particular abi was requested, it still needs to be supported by the build.
- if ((!mSkipHostArchCheck && !archAbis.contains(mAbiName)) ||
- !AbiUtils.isAbiSupportedByCompatibility(mAbiName)) {
- throw new IllegalArgumentException(String.format("Your CTS hasn't been built with "
- + "abi '%s' support, this CTS currently supports '%s'.",
- mAbiName, archAbis));
- } else {
- abis.add(new Abi(mAbiName, AbiUtils.getBitness(mAbiName)));
- return abis;
- }
- } else {
- // Run on all abi in common between the device and CTS.
- List<String> deviceAbis = Arrays.asList(AbiFormatter.getSupportedAbis(device, ""));
- for (String abi : deviceAbis) {
- if ((mSkipHostArchCheck || archAbis.contains(abi)) &&
- AbiUtils.isAbiSupportedByCompatibility(abi)) {
- abis.add(new Abi(abi, AbiUtils.getBitness(abi)));
- } else {
- CLog.d("abi '%s' is supported by device but not by this CTS build (%s), tests "
- + "will not run against it.", abi, archAbis);
- }
- }
- if (abis.isEmpty()) {
- throw new IllegalArgumentException(String.format("None of the abi supported by this"
- + " CTS build ('%s') are supported by the device ('%s').",
- archAbis, deviceAbis));
- }
- return abis;
- }
- }
-
- /**
- * Return the abis supported by the Host build target architecture.
- * Exposed for testing.
- */
- protected Set<String> getAbisForBuildTargetArch() {
- return AbiUtils.getAbisForArch(TestSuiteInfo.getInstance().getTargetArch());
- }
-
- /**
* Sets the include/exclude filters up based on if a module name was given or whether this is a
* retry run.
*/
@@ -273,7 +170,8 @@
String moduleName = modules.get(0);
checkFilters(mIncludeFilters, moduleName);
checkFilters(mExcludeFilters, moduleName);
- mIncludeFilters.add(new TestFilter(mAbiName, moduleName, mTestName).toString());
+ mIncludeFilters.add(new TestFilter(getRequestedAbi(), moduleName, mTestName)
+ .toString());
}
} else if (mTestName != null) {
throw new IllegalArgumentException(
@@ -306,4 +204,11 @@
public void setExcludeFilter(Set<String> excludeFilters) {
mExcludeFilters.addAll(excludeFilters);
}
+
+ /**
+ * Allow to reset the requested session id for retry.
+ */
+ public void resetRetryId() {
+ mRetrySessionId = null;
+ }
}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
index a889330..9693ce0 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuite.java
@@ -30,7 +30,6 @@
import com.android.tradefed.testtype.ITestFilterReceiver;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.StreamUtil;
import java.io.File;
@@ -42,7 +41,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -67,9 +65,7 @@
*/
public LinkedHashMap<String, IConfiguration> loadConfigs(File testsDir, Set<IAbi> abis,
List<String> testArgs, List<String> moduleArgs,
- Set<String> includeFilters, Set<String> excludeFilters,
- MultiMap<String, String> metadataIncludeFilters,
- MultiMap<String, String> metadataExcludeFilters) {
+ Set<String> includeFilters, Set<String> excludeFilters) {
CLog.d("Initializing ModuleRepo\nTests Dir:%s\nABIs:%s\n" +
"Test Args:%s\nModule Args:%s\nIncludes:%s\nExcludes:%s",
testsDir.getAbsolutePath(), abis, testArgs, moduleArgs,
@@ -108,12 +104,6 @@
continue;
}
IConfiguration config = mConfigFactory.createConfigurationFromArgs(pathArg);
- if (!filterByConfigMetadata(config,
- metadataIncludeFilters, metadataExcludeFilters)) {
- // if the module config did not pass the metadata filters, it's excluded
- // from execution
- continue;
- }
Map<String, List<String>> args = new HashMap<>();
if (mModuleArgs.containsKey(name)) {
args.putAll(mModuleArgs.get(name));
@@ -176,47 +166,6 @@
}
}
- @VisibleForTesting
- protected boolean filterByConfigMetadata(IConfiguration config,
- MultiMap<String, String> include, MultiMap<String, String> exclude) {
- MultiMap<String, String> metadata = config.getConfigurationDescription().getAllMetaData();
- boolean shouldInclude = false;
- for (String key : include.keySet()) {
- Set<String> filters = new HashSet<>(include.get(key));
- if (metadata.containsKey(key)) {
- filters.retainAll(metadata.get(key));
- if (!filters.isEmpty()) {
- // inclusion filter is not empty and there's at least one matching inclusion
- // rule so there's no need to match other inclusion rules
- shouldInclude = true;
- break;
- }
- }
- }
- if (!include.isEmpty() && !shouldInclude) {
- // if inclusion filter is not empty and we didn't find a match, the module will not be
- // included
- return false;
- }
- // Now evaluate exclusion rules, this ordering also means that exclusion rules may override
- // inclusion rules: a config already matched for inclusion may still be excluded if matching
- // rules exist
- for (String key : exclude.keySet()) {
- Set<String> filters = new HashSet<>(exclude.get(key));
- if (metadata.containsKey(key)) {
- filters.retainAll(metadata.get(key));
- if (!filters.isEmpty()) {
- // we found at least one matching exclusion rules, so we are excluding this
- // this module
- return false;
- }
- }
- }
- // we've matched at least one inclusion rule (if there's any) AND we didn't match any of the
- // exclusion rules (if there's any)
- return true;
- }
-
/**
* @return the {@link List} of modules whose name contains the given pattern.
*/
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 5aaa542..960d788 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -33,14 +33,12 @@
import com.android.compatibility.common.tradefed.targetprep.MediaPreparerTest;
import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
import com.android.compatibility.common.tradefed.targetprep.SettingsPreparerTest;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBaseTest;
import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
import com.android.compatibility.common.tradefed.testtype.JarHostTestTest;
import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
import com.android.compatibility.common.tradefed.testtype.SubPlanTest;
import com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTestTest;
-import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuiteTest;
import com.android.compatibility.common.tradefed.testtype.suite.ModuleRepoSuiteTest;
import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
import com.android.compatibility.common.tradefed.util.DynamicConfigFileReaderTest;
@@ -90,7 +88,6 @@
SettingsPreparerTest.class,
// testtype
- CompatibilityHostTestBaseTest.class,
CompatibilityTestTest.class,
JarHostTestTest.class,
ModuleDefTest.class,
@@ -101,7 +98,6 @@
RetryFactoryTestTest.class,
// testype.suite
- CompatibilityTestSuiteTest.class,
ModuleRepoSuiteTest.class,
// util
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
index 829dcc0..1a3c723 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
@@ -178,6 +178,7 @@
* references and not absolute path. When sharding, path are invalidated but Files are copied.
*/
public void testAddDynamicFiles() throws Exception {
+ createDirStructure();
File tmpDynamicFile = FileUtil.createTempFile("cts-test-file", ".dynamic");
FileUtil.writeToFile("test string", tmpDynamicFile);
try {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
index 50c5da3..09e16a6 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildProviderTest.java
@@ -15,7 +15,6 @@
*/
package com.android.compatibility.common.tradefed.build;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -69,7 +68,10 @@
EasyMock.replay(mMockDevice);
IBuildInfo info = mProvider.getBuild(mMockDevice);
EasyMock.verify(mMockDevice);
- assertFalse(info instanceof IDeviceBuildInfo);
+ // Still creates a device build for us.
+ assertTrue(info instanceof IDeviceBuildInfo);
+ // tests dir should be populated
+ assertNotNull(((IDeviceBuildInfo)info).getTestsDir());
}
/**
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
index bcc82ac9..644ae22 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
@@ -32,6 +32,8 @@
import org.junit.Assert;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.io.File;
import java.io.FilenameFilter;
@@ -43,6 +45,7 @@
/**
* Test that configuration in CTS can load and have expected properties.
*/
+@RunWith(JUnit4.class)
public class CtsConfigLoadingTest {
private static final String METADATA_COMPONENT = "component";
@@ -69,6 +72,7 @@
"neuralnetworks",
"renderscript",
"security",
+ "statsd",
"systems",
"sysui",
"telecom",
@@ -79,6 +83,22 @@
));
/**
+ * List of the officially supported runners in CTS, they meet all the interfaces criteria as
+ * well as support sharding very well. Any new addition should go through a review.
+ */
+ private static final Set<String> SUPPORTED_CTS_TEST_TYPE = new HashSet<>(Arrays.asList(
+ // Cts runners
+ "com.android.compatibility.common.tradefed.testtype.JarHostTest",
+ "com.android.compatibility.testtype.DalvikTest",
+ "com.android.compatibility.testtype.LibcoreTest",
+ "com.drawelements.deqp.runner.DeqpTestRunner",
+ // Tradefed runners
+ "com.android.tradefed.testtype.AndroidJUnitTest",
+ "com.android.tradefed.testtype.HostTest",
+ "com.android.tradefed.testtype.GTest"
+ ));
+
+ /**
* Test that configuration shipped in Tradefed can be parsed.
* -> Exclude deprecated ApkInstaller.
* -> Check if host-side tests are non empty.
@@ -121,6 +141,12 @@
}
// We can ensure that Host side tests are not empty.
for (IRemoteTest test : c.getTests()) {
+ // Check that all the tests runners are well supported.
+ if (!SUPPORTED_CTS_TEST_TYPE.contains(test.getClass().getCanonicalName())) {
+ throw new ConfigurationException(
+ String.format("testtype %s is not officially supported by CTS.",
+ test.getClass().getCanonicalName()));
+ }
if (test instanceof HostTest) {
HostTest hostTest = (HostTest) test;
// We inject a made up folder so that it can find the tests.
@@ -148,6 +174,13 @@
Assert.assertTrue(String.format("Module config contains unknown \"component\" metadata "
+ "field \"%s\", supported ones are: %s\nconfig: %s",
cmp, KNOWN_COMPONENTS, config), KNOWN_COMPONENTS.contains(cmp));
+
+ // Ensure each CTS module is tagged with <option name="test-suite-tag" value="cts" />
+ Assert.assertTrue(String.format(
+ "Module config %s does not contains "
+ + "'<option name=\"test-suite-tag\" value=\"cts\" />'", config.getName()),
+ cd.getSuiteTags().contains("cts"));
+
// Check not-shardable: JarHostTest cannot create empty shards so it should never need
// to be not-shardable.
if (cd.isNotShardable()) {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBaseTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBaseTest.java
deleted file mode 100644
index 66d05fc..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/CompatibilityHostTestBaseTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.tradefed.testtype;
-
-import com.android.ddmlib.IDevice;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestRunResult;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.JUnit4ResultForwarder;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.testtype.IDeviceTest;
-
-import junit.framework.TestCase;
-
-import org.easymock.EasyMock;
-import org.junit.Test;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.RunWith;
-import org.junit.runner.Runner;
-
-import java.util.Collections;
-
-/**
- * Tests for the CompatibilityHostTestBase class.
- */
-public class CompatibilityHostTestBaseTest extends TestCase {
-
- private static final String DEVICE_TEST_PKG = "com.android.foo";
-
- @RunWith(DeviceJUnit4ClassRunner.class)
- public static class MockTest extends CompatibilityHostTestBase {
-
- @Test
- public void testRunDeviceTests() throws Exception {
- runDeviceTests(DEVICE_TEST_PKG, null, null);
- }
-
- @Override
- protected CollectingTestListener createCollectingListener() {
- return new CollectingTestListener() {
- @Override
- public TestRunResult getCurrentRunResults() {
- TestRunResult result = new TestRunResult();
- TestIdentifier t1 = new TestIdentifier("class1", "test1");
- result.testStarted(t1);
- result.testEnded(t1, Collections.emptyMap());
- return result;
- }
- };
- }
-
- }
-
- public void testRunMockDeviceTests() throws Exception {
- final TestIdentifier testRunDeviceTests =
- new TestIdentifier(MockTest.class.getName(), "testRunDeviceTests");
-
- ITestInvocationListener listener = EasyMock.createStrictMock(ITestInvocationListener.class);
- ITestDevice device = EasyMock.createMock(ITestDevice.class);
-
- listener.testStarted(testRunDeviceTests);
- EasyMock.expect(device.getIDevice()).andReturn(EasyMock.createMock(IDevice.class)).once();
- EasyMock.expect(device.runInstrumentationTests((RemoteAndroidTestRunner)
- EasyMock.anyObject(), (CollectingTestListener) EasyMock.anyObject())).andReturn(
- true).once();
- listener.testEnded(testRunDeviceTests, Collections.emptyMap());
- EasyMock.replay(listener, device);
-
- JUnitCore runnerCore = new JUnitCore();
- runnerCore.addListener(new JUnit4ResultForwarder(listener));
- Runner checkRunner = Request.aClass(MockTest.class).getRunner();
- ((IDeviceTest) checkRunner).setDevice(device);
- ((IBuildReceiver) checkRunner).setBuild(EasyMock.createMock(IBuildInfo.class));
- ((IAbiReceiver) checkRunner).setAbi(EasyMock.createMock(IAbi.class));
- runnerCore.run(checkRunner);
- EasyMock.verify(listener, device);
- }
-
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
index a06bca0..064cee4 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/JarHostTestTest.java
@@ -15,19 +15,28 @@
*/
package com.android.compatibility.common.tradefed.testtype;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
import com.android.tradefed.testtype.HostTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.FileUtil;
-import junit.framework.TestCase;
-
import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -39,17 +48,21 @@
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Unit tests for {@link JarHostTest}.
*/
-public class JarHostTestTest extends TestCase {
+@RunWith(JUnit4.class)
+public class JarHostTestTest {
private static final String TEST_JAR1 = "/testtype/testJar1.jar";
private static final String TEST_JAR2 = "/testtype/testJar2.jar";
private JarHostTest mTest;
private File mTestDir = null;
+ private ITestInvocationListener mListener;
/**
* More testable version of {@link JarHostTest}
@@ -74,23 +87,16 @@
}
}
- /**
- * {@inheritDoc}
- */
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
mTest = new JarHostTest();
mTestDir = FileUtil.createTempDir("jarhostest");
+ mListener = EasyMock.createMock(ITestInvocationListener.class);
}
- /**
- * {@inheritDoc}
- */
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
FileUtil.recursiveDelete(mTestDir);
- super.tearDown();
}
/**
@@ -123,13 +129,18 @@
@RunWith(JUnit4.class)
public static class Junit4TestClass2 {
public Junit4TestClass2() {}
+ @Rule public TestMetrics metrics = new TestMetrics();
+
@org.junit.Test
- public void testPass2() {}
+ public void testPass2() {
+ metrics.addTestMetric("key", "value");
+ }
}
/**
* Test that {@link JarHostTest#split()} inherited from {@link HostTest} is still good.
*/
+ @Test
public void testSplit_withoutJar() throws Exception {
OptionSetter setter = new OptionSetter(mTest);
setter.setOptionValue("class", "com.android.compatibility.common.tradefed.testtype."
@@ -146,6 +157,7 @@
/**
* Test that {@link JarHostTest#split()} can split classes coming from a jar.
*/
+ @Test
public void testSplit_withJar() throws Exception {
File testJar = getJarResource(TEST_JAR1, mTestDir);
mTest = new JarHostTestable(mTestDir);
@@ -165,6 +177,7 @@
/**
* Test that {@link JarHostTest#getTestShard(int, int)} can split classes coming from a jar.
*/
+ @Test
public void testGetTestShard_withJar() throws Exception {
File testJar = getJarResource(TEST_JAR2, mTestDir);
mTest = new JarHostTestLoader(mTestDir, testJar);
@@ -259,4 +272,45 @@
return child;
}
}
+
+ /**
+ * If a jar file is not found, the countTest will fail but we still want to report a
+ * testRunStart and End pair for results.
+ */
+ @Test
+ public void testCountTestFails() throws Exception {
+ OptionSetter setter = new OptionSetter(mTest);
+ setter.setOptionValue("jar", "thisjardoesnotexistatall.jar");
+ mListener.testRunStarted(EasyMock.anyObject(), EasyMock.eq(0));
+ mListener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
+ EasyMock.replay(mListener);
+ try {
+ mTest.run(mListener);
+ fail("Should have thrown an exception.");
+ } catch(RuntimeException expected) {
+ // expected
+ }
+ EasyMock.verify(mListener);
+ }
+
+ /**
+ * Test that metrics from tests in JarHost are reported and accounted for.
+ */
+ @Test
+ public void testJarHostMetrics() throws Exception {
+ OptionSetter setter = new OptionSetter(mTest);
+ setter.setOptionValue("class", "com.android.compatibility.common.tradefed.testtype."
+ + "JarHostTestTest$Junit4TestClass2");
+ mListener.testRunStarted(EasyMock.anyObject(), EasyMock.eq(1));
+ TestIdentifier tid = new TestIdentifier("com.android.compatibility.common.tradefed."
+ + "testtype.JarHostTestTest$Junit4TestClass2", "testPass2");
+ mListener.testStarted(EasyMock.eq(tid), EasyMock.anyLong());
+ Map<String, String> metrics = new HashMap<>();
+ metrics.put("key", "value");
+ mListener.testEnded(EasyMock.eq(tid), EasyMock.anyLong(), EasyMock.eq(metrics));
+ mListener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
+ EasyMock.replay(mListener);
+ mTest.run(mListener);
+ EasyMock.verify(mListener);
+ }
}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
index d67619a..682088c 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
@@ -15,10 +15,9 @@
*/
package com.android.compatibility.common.tradefed.testtype.retry;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
import com.android.compatibility.common.tradefed.testtype.suite.CompatibilityTestSuite;
import com.android.compatibility.common.tradefed.util.RetryFilterHelper;
import com.android.tradefed.config.IConfiguration;
@@ -52,7 +51,7 @@
private RetryFilterHelper mSpyFilter;
/**
- * A {@link CompatibilityTest} that does not run anything.
+ * A {@link CompatibilityTestSuite} that does not run anything.
*/
@OptionClass(alias = "compatibility")
public static class VoidCompatibilityTest extends CompatibilityTestSuite {
@@ -90,7 +89,7 @@
};
mFactory = new RetryFactoryTest() {
@Override
- RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
+ protected RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
return mSpyFilter;
}
@Override
@@ -108,6 +107,7 @@
public void testRetry_receiveOption() throws Exception {
OptionSetter setter = new OptionSetter(mFactory);
setter.setOptionValue("retry", "10599");
+ setter.setOptionValue("test-arg", "abcd");
EasyMock.replay(mMockListener);
mFactory.run(mMockListener);
EasyMock.verify(mMockListener);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java
deleted file mode 100644
index 86569ef..0000000
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuiteTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.tradefed.testtype.suite;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.util.AbiUtils;
-
-import org.easymock.EasyMock;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Unit tests for {@link CompatibilityTestSuite}.
- */
-public class CompatibilityTestSuiteTest {
-
- private static final String FAKE_HOST_ARCH = "arm";
- private CompatibilityTestSuite mTest;
- private ITestDevice mMockDevice;
-
- @Before
- public void setUp() throws Exception {
- mTest = new CompatibilityTestSuite() {
- @Override
- protected Set<String> getAbisForBuildTargetArch() {
- return AbiUtils.getAbisForArch(FAKE_HOST_ARCH);
- }
- };
- mMockDevice = EasyMock.createMock(ITestDevice.class);
- mTest.setDevice(mMockDevice);
- }
-
- /**
- * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning a proper
- * intersection of CTS supported architectures and Device supported architectures.
- */
- @Test
- public void testGetAbis() throws DeviceNotAvailableException {
- EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
- .andReturn("arm64-v8a,armeabi-v7a,armeabi");
- Set<String> expectedAbis = new HashSet<>();
- expectedAbis.add("arm64-v8a");
- expectedAbis.add("armeabi-v7a");
- EasyMock.replay(mMockDevice);
- Set<IAbi> res = mTest.getAbis(mMockDevice);
- assertEquals(2, res.size());
- for (IAbi abi : res) {
- assertTrue(expectedAbis.contains(abi.getName()));
- }
- EasyMock.verify(mMockDevice);
- }
-
- /**
- * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is throwing an exception when
- * none of the CTS build supported abi match the device abi.
- */
- @Test
- public void testGetAbis_notSupported() throws DeviceNotAvailableException {
- EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
- .andReturn("armeabi");
- EasyMock.replay(mMockDevice);
- try {
- mTest.getAbis(mMockDevice);
- fail("Should have thrown an exception");
- } catch (IllegalArgumentException e) {
- assertEquals("None of the abi supported by this CTS build ('[armeabi-v7a, arm64-v8a]')"
- + " are supported by the device ('[armeabi]').", e.getMessage());
- }
- EasyMock.verify(mMockDevice);
- }
-
- /**
- * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning only the device
- * primary abi.
- */
- @Test
- public void testGetAbis_primaryAbiOnly() throws Exception {
- OptionSetter setter = new OptionSetter(mTest);
- setter.setOptionValue(CompatibilityTest.PRIMARY_ABI_RUN, "true");
- EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abi")))
- .andReturn("arm64-v8a");
- Set<String> expectedAbis = new HashSet<>();
- expectedAbis.add("arm64-v8a");
- EasyMock.replay(mMockDevice);
- Set<IAbi> res = mTest.getAbis(mMockDevice);
- assertEquals(1, res.size());
- for (IAbi abi : res) {
- assertTrue(expectedAbis.contains(abi.getName()));
- }
- EasyMock.verify(mMockDevice);
- }
-
- /**
- * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is throwing an exception if
- * the primary abi is not supported.
- */
- @Test
- public void testGetAbis_primaryAbiOnly_NotSupported() throws Exception {
- OptionSetter setter = new OptionSetter(mTest);
- setter.setOptionValue(CompatibilityTest.PRIMARY_ABI_RUN, "true");
- EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abi")))
- .andReturn("armeabi");
- EasyMock.replay(mMockDevice);
- try {
- mTest.getAbis(mMockDevice);
- fail("Should have thrown an exception");
- } catch (IllegalArgumentException e) {
- assertEquals("Your CTS hasn't been built with abi 'armeabi' support, "
- + "this CTS currently supports '[armeabi-v7a, arm64-v8a]'.", e.getMessage());
- }
- EasyMock.verify(mMockDevice);
- }
-
- /**
- * Test that {@link CompatibilityTestSuite#getAbis(ITestDevice)} is returning the list of abi
- * supported by Compatibility and the device, and not the particular CTS build.
- */
- @Test
- public void testGetAbis_skipCtsArchCheck() throws Exception {
- OptionSetter setter = new OptionSetter(mTest);
- setter.setOptionValue(CompatibilityTest.SKIP_HOST_ARCH_CHECK, "true");
- EasyMock.expect(mMockDevice.getProperty(EasyMock.eq("ro.product.cpu.abilist")))
- .andReturn("x86_64,x86,armeabi");
- Set<String> expectedAbis = new HashSet<>();
- expectedAbis.add("x86_64");
- expectedAbis.add("x86");
- EasyMock.replay(mMockDevice);
- Set<IAbi> res = mTest.getAbis(mMockDevice);
- assertEquals(2, res.size());
- for (IAbi abi : res) {
- assertTrue(expectedAbis.contains(abi.getName()));
- }
- EasyMock.verify(mMockDevice);
- }
-
- /**
- * Test {@link CompatibilityTestSuite#getAbis(ITestDevice)} when we skip the Cts side
- * architecture check and want to run x86 abi.
- */
- @Test
- public void testGetAbis_skipCtsArchCheck_abiSpecified() throws Exception {
- OptionSetter setter = new OptionSetter(mTest);
- setter.setOptionValue(CompatibilityTest.SKIP_HOST_ARCH_CHECK, "true");
- setter.setOptionValue(CompatibilityTest.ABI_OPTION, "x86");
- Set<String> expectedAbis = new HashSet<>();
- expectedAbis.add("x86");
- EasyMock.replay(mMockDevice);
- Set<IAbi> res = mTest.getAbis(mMockDevice);
- assertEquals(1, res.size());
- for (IAbi abi : res) {
- assertTrue(expectedAbis.contains(abi.getName()));
- }
- EasyMock.verify(mMockDevice);
- }
-}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java
index c67afc0..31db704 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/suite/ModuleRepoSuiteTest.java
@@ -16,17 +16,13 @@
package com.android.compatibility.common.tradefed.testtype.suite;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import com.android.tradefed.config.Configuration;
-import com.android.tradefed.config.ConfigurationDescriptor;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.testtype.IRemoteTest;
-import com.android.tradefed.util.MultiMap;
import org.junit.Before;
import org.junit.Test;
@@ -44,8 +40,6 @@
@RunWith(JUnit4.class)
public class ModuleRepoSuiteTest {
- private static final MultiMap<String, String> METADATA_INCLUDES = new MultiMap<>();
- private static final MultiMap<String, String> METADATA_EXCLUDES = new MultiMap<>();
private ModuleRepoSuite mRepo;
@Before
@@ -53,261 +47,6 @@
mRepo = new ModuleRepoSuite();
}
- /**
- * When there are no metadata based filters specified, config should be included.
- */
- @Test
- public void testMetadataFilter_emptyFilters() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- assertTrue("config not included when metadata filters are empty",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, METADATA_EXCLUDES));
- }
-
- /**
- * When inclusion filter is specified, config matching the filter is included.
- */
- @Test
- public void testMetadataFilter_matchInclude() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "foo");
- assertTrue("config not included with matching inclusion filter",
- mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
- }
-
- /**
- * When inclusion filter is specified, config not matching the filter is excluded
- */
- @Test
- public void testMetadataFilter_noMatchInclude_mismatchValue() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "bar");
- assertFalse("config not excluded with mismatching inclusion filter",
- mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
- }
-
- /**
- * When inclusion filter is specified, config not matching the filter is excluded.
- */
- @Test
- public void testMetadataFilter_noMatchInclude_mismatchKey() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("group", "bar");
- assertFalse("config not excluded with mismatching inclusion filter",
- mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
- }
-
- /**
- * When exclusion filter is specified, config matching the filter is excluded.
- */
- @Test
- public void testMetadataFilter_matchExclude() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("component", "foo");
- assertFalse("config not excluded with matching exclusion filter",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
- }
-
- /**
- * When exclusion filter is specified, config not matching the filter is included.
- */
- @Test
- public void testMetadataFilter_noMatchExclude_mismatchKey() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("component", "bar");
- assertTrue("config not included with mismatching exclusion filter",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
- }
-
- /**
- * When exclusion filter is specified, config not matching the filter is included.
- */
- @Test
- public void testMetadataFilter_noMatchExclude_mismatchValue() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("group", "bar");
- assertTrue("config not included with mismatching exclusion filter",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
- }
-
- /**
- * When inclusion filter is specified, config with one of the metadata field matching the filter
- * is included.
- */
- @Test
- public void testMetadataFilter_matchInclude_multipleMetadataField() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- metadata.put("component", "bar");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "foo");
- assertTrue("config not included with matching inclusion filter",
- mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
- }
-
- /**
- * When exclusion filter is specified, config with one of the metadata field matching the filter
- * is excluded.
- */
- @Test
- public void testMetadataFilter_matchExclude_multipleMetadataField() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- metadata.put("component", "bar");
- desc.setMetaData(metadata);
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("component", "foo");
- assertFalse("config not excluded with matching exclusion filter",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
- }
-
- /**
- * When inclusion filters are specified, config with metadata field matching one of the filter
- * is included.
- */
- @Test
- public void testMetadataFilter_matchInclude_multipleFilters() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "foo");
- includeFilter.put("component", "bar");
- assertTrue("config not included with matching inclusion filter",
- mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
- }
-
- /**
- * When exclusion filters are specified, config with metadata field matching one of the filter
- * is excluded.
- */
- @Test
- public void testMetadataFilter_matchExclude_multipleFilters() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- desc.setMetaData(metadata);
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("component", "foo");
- excludeFilter.put("component", "bar");
- assertFalse("config not excluded with matching exclusion filter",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
- }
-
- /**
- * When inclusion filters are specified, config with metadata field matching one of the filter
- * is included.
- */
- @Test
- public void testMetadataFilter_matchInclude_multipleMetadataAndFilters() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo1");
- metadata.put("group", "bar1");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "foo1");
- includeFilter.put("group", "bar2");
- assertTrue("config not included with matching inclusion filter",
- mRepo.filterByConfigMetadata(config, includeFilter, METADATA_EXCLUDES));
- }
-
- /**
- * When exclusion filters are specified, config with metadata field matching one of the filter
- * is excluded.
- */
- @Test
- public void testMetadataFilter_matchExclude_multipleMetadataAndFilters() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo1");
- metadata.put("group", "bar1");
- desc.setMetaData(metadata);
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("component", "foo1");
- excludeFilter.put("group", "bar2");
- assertFalse("config not excluded with matching exclusion filter",
- mRepo.filterByConfigMetadata(config, METADATA_INCLUDES, excludeFilter));
- }
-
- /**
- * When inclusion and exclusion filters are both specified, config can pass through the filters
- * as expected.
- */
- @Test
- public void testMetadataFilter_includeAndExclude() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- metadata.put("group", "bar1");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "foo");
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("group", "bar2");
- assertTrue("config not included with matching inclusion and mismatching exclusion filters",
- mRepo.filterByConfigMetadata(config, includeFilter, excludeFilter));
- }
-
- /**
- * When inclusion and exclusion filters are both specified, config be excluded as specified
- */
- @Test
- public void testMetadataFilter_includeThenExclude() throws Exception {
- IConfiguration config = new Configuration("foo", "bar");
- ConfigurationDescriptor desc = config.getConfigurationDescription();
- MultiMap<String, String> metadata = new MultiMap<>();
- metadata.put("component", "foo");
- metadata.put("group", "bar");
- desc.setMetaData(metadata);
- MultiMap<String, String> includeFilter = new MultiMap<>();
- includeFilter.put("component", "foo");
- MultiMap<String, String> excludeFilter = new MultiMap<>();
- excludeFilter.put("group", "bar");
- assertFalse("config not excluded with matching inclusion and exclusion filters",
- mRepo.filterByConfigMetadata(config, includeFilter, excludeFilter));
- }
-
public static class TestInject implements IRemoteTest {
@Option(name = "simple-string")
public String test = null;
diff --git a/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java b/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
index 5d303e5..369189d 100644
--- a/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
+++ b/common/host-side/util/src/com/android/compatibility/common/util/DeviceInfo.java
@@ -18,13 +18,12 @@
import static org.junit.Assert.fail;
import com.android.compatibility.common.util.HostInfoStore;
-import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
-import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
@@ -38,37 +37,18 @@
* Collect device information from host and write to a JSON file.
*/
@RunWith(DeviceJUnit4ClassRunner.class)
-public abstract class DeviceInfo implements IDeviceTest {
+public abstract class DeviceInfo extends BaseHostJUnit4Test {
// Default name of the directory for storing device info files within the result directory
public static final String RESULT_DIR_NAME = "device-info-files";
public static final String FILE_SUFFIX = ".deviceinfo.json";
- /** A reference to the device under test. */
- protected ITestDevice mDevice;
-
private HostInfoStore mStore;
@Rule
public TestLogData mLogger = new TestLogData();
- /**
- * {@inheritDoc}
- */
- @Override
- public ITestDevice getDevice() {
- return mDevice;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setDevice(ITestDevice device) {
- mDevice = device;
- }
-
@Test
public void testCollectDeviceInfo() throws Exception {
String deviceInfoName = getClass().getSimpleName() + FILE_SUFFIX;
diff --git a/common/util/src/com/android/compatibility/common/util/LogcatInspector.java b/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
new file mode 100644
index 0000000..ed82307
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/LogcatInspector.java
@@ -0,0 +1,130 @@
+package com.android.compatibility.common.util;
+
+import static junit.framework.TestCase.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.io.Closeables;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Inherit this class and implement {@link #executeShellCommand(String)} to be able to assert that
+ * logcat contains what you want.
+ */
+public abstract class LogcatInspector {
+ private static final int SMALL_LOGCAT_DELAY = 1000;
+
+ /**
+ * Should execute adb shell {@param command} and return an {@link InputStream} with the result.
+ */
+ protected abstract InputStream executeShellCommand(String command) throws IOException;
+
+ /**
+ * Logs an unique string using tag {@param tag} and wait until it appears to continue execution.
+ *
+ * @return a unique separator string.
+ * @throws IOException if error while executing command.
+ */
+ public String mark(String tag) throws IOException {
+ String uniqueString = ":::" + UUID.randomUUID().toString();
+ executeShellCommand("log -t " + tag + " " + uniqueString);
+ // This is to guarantee that we only return after the string has been logged, otherwise
+ // in practice the case where calling Log.?(<message1>) right after clearAndMark() resulted
+ // in <message1> appearing before the unique identifier. It's not guaranteed per the docs
+ // that log command will have written when returning, so better be safe. 5s should be fine.
+ assertLogcatContainsInOrder(tag + ":* *:S", 5, uniqueString);
+ return uniqueString;
+ }
+
+ /**
+ * Wait for up to {@param maxTimeoutInSeconds} for the given {@param logcatStrings} strings to
+ * appear in logcat in the given order. By passing the separator returned by {@link
+ * #mark(String)} as the first string you can ensure that only logs emitted after that
+ * call to mark() are found. Repeated strings are not supported.
+ *
+ * @throws AssertionError if the strings are not found in the given time.
+ * @throws IOException if error while reading.
+ */
+ public void assertLogcatContainsInOrder(
+ String filterSpec, int maxTimeoutInSeconds, String... logcatStrings)
+ throws AssertionError, IOException {
+ try {
+ int nextStringIndex =
+ numberOfLogcatStringsFound(filterSpec, maxTimeoutInSeconds, logcatStrings);
+ if (nextStringIndex < logcatStrings.length) {
+ fail(
+ "Couldn't find "
+ + logcatStrings[nextStringIndex]
+ + (nextStringIndex > 0
+ ? " after " + logcatStrings[nextStringIndex - 1]
+ : "")
+ + " within "
+ + maxTimeoutInSeconds
+ + " seconds ");
+ }
+ } catch (InterruptedException e) {
+ fail("Thread interrupted unexpectedly: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Wait for up to {@param timeInSeconds}, if all the strings {@param logcatStrings} are found in
+ * order then the assertion fails, otherwise it succeeds.
+ *
+ * @throws AssertionError if all the strings are found in order in the given time.
+ * @throws IOException if error while reading.
+ */
+ public void assertLogcatDoesNotContainInOrder(int timeInSeconds, String... logcatStrings)
+ throws IOException {
+ try {
+ int stringsFound = numberOfLogcatStringsFound("", timeInSeconds, logcatStrings);
+ if (stringsFound == logcatStrings.length) {
+ fail("Found " + Joiner.on(", ").join(logcatStrings) + " that weren't expected");
+ }
+ } catch (InterruptedException e) {
+ fail("Thread interrupted unexpectedly: " + e.getMessage());
+ }
+ }
+
+ private int numberOfLogcatStringsFound(
+ String filterSpec, int timeInSeconds, String... logcatStrings)
+ throws InterruptedException, IOException {
+ long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(timeInSeconds);
+ int stringIndex = 0;
+ while (timeout >= System.currentTimeMillis()) {
+ InputStream logcatStream = executeShellCommand("logcat -v brief -d " + filterSpec);
+ BufferedReader logcat = new BufferedReader(new InputStreamReader(logcatStream));
+ String line;
+ stringIndex = 0;
+ while ((line = logcat.readLine()) != null) {
+ if (line.contains(logcatStrings[stringIndex])) {
+ stringIndex++;
+ if (stringIndex >= logcatStrings.length) {
+ drainAndClose(logcat);
+ return stringIndex;
+ }
+ }
+ }
+ Closeables.closeQuietly(logcat);
+ // In case the key has not been found, wait for the log to update before
+ // performing the next search.
+ Thread.sleep(SMALL_LOGCAT_DELAY);
+ }
+ return stringIndex;
+ }
+
+ private static void drainAndClose(BufferedReader reader) {
+ try {
+ while (reader.read() >= 0) {
+ // do nothing.
+ }
+ } catch (IOException ignored) {
+ }
+ Closeables.closeQuietly(reader);
+ }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 4308947..c394b8d 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -25,7 +25,6 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
@@ -44,6 +43,7 @@
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;
+
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
@@ -115,17 +115,20 @@
private static final String SUMMARY_TAG = "Summary";
private static final String TEST_TAG = "Test";
+ private static final String LATEST_RESULT_DIR = "latest";
/**
* Returns IInvocationResults that can be queried for general reporting information, but that
* do not store underlying module data. Useful for summarizing invocation history.
* @param resultsDir
- * @param useChecksum
*/
public static List<IInvocationResult> getLightResults(File resultsDir) {
List<IInvocationResult> results = new ArrayList<>();
List<File> files = getResultDirectories(resultsDir);
for (File resultDir : files) {
+ if (LATEST_RESULT_DIR.equals(resultDir.getName())) {
+ continue;
+ }
IInvocationResult result = getResultFromDir(resultDir, false);
if (result != null) {
results.add(new LightInvocationResult(result));
@@ -491,8 +494,7 @@
/**
* Find the IInvocationResult for the given sessionId.
*/
- public static IInvocationResult findResult(File resultsDir, Integer sessionId)
- throws FileNotFoundException {
+ public static IInvocationResult findResult(File resultsDir, Integer sessionId) {
return findResult(resultsDir, sessionId, true);
}
@@ -500,7 +502,7 @@
* Find the IInvocationResult for the given sessionId.
*/
private static IInvocationResult findResult(
- File resultsDir, Integer sessionId, Boolean useChecksum) throws FileNotFoundException {
+ File resultsDir, Integer sessionId, Boolean useChecksum) {
if (sessionId < 0) {
throw new IllegalArgumentException(
String.format("Invalid session id [%d] ", sessionId));
@@ -532,7 +534,7 @@
/**
* Get a list of child directories that contain test invocation results
* @param resultsDir the root test result directory
- * @return
+ * @return the list of {@link File} results directory.
*/
public static List<File> getResultDirectories(File resultsDir) {
List<File> directoryList = new ArrayList<>();
diff --git a/error_prone_rules_tests.mk b/error_prone_rules_tests.mk
index d17828d..7c729ec 100644
--- a/error_prone_rules_tests.mk
+++ b/error_prone_rules_tests.mk
@@ -17,9 +17,13 @@
# Goal is to eventually merge with error_prone_rules.mk
LOCAL_ERROR_PRONE_FLAGS:= -Xep:ArrayToString:ERROR \
-Xep:CollectionIncompatibleType:ERROR \
+ -Xep:EqualsIncompatibleType:ERROR \
-Xep:EqualsNaN:ERROR \
-Xep:FormatString:ERROR \
+ -Xep:IdentityBinaryExpression:ERROR \
-Xep:JUnit3TestNotRun:ERROR \
+ -Xep:JUnitAmbiguousTestClass:ERROR \
+ -Xep:MissingFail:ERROR \
-Xep:SizeGreaterThanOrEqualsZero:ERROR \
-Xep:TryFailThrowable:ERROR
diff --git a/hostsidetests/aadb/AndroidTest.xml b/hostsidetests/aadb/AndroidTest.xml
index 2224df2..a98ee5a 100644
--- a/hostsidetests/aadb/AndroidTest.xml
+++ b/hostsidetests/aadb/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS aadb host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest">
<option name="jar" value="CtsAadbHostTestCases.jar" />
diff --git a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java
index a570232..6719923 100644
--- a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java
+++ b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceFuncTest.java
@@ -57,16 +57,14 @@
* Simple testcase to ensure that the grabbing a bugreport from a real TestDevice works.
*/
public void testBugreport() throws Exception {
- InputStreamSource bugreport = mTestDevice.getBugreport();
InputStream bugreportData = null;
- try {
+ try (InputStreamSource bugreport = mTestDevice.getBugreport()) {
bugreportData = bugreport.createInputStream();
String data = StreamUtil.getStringFromStream(bugreportData);
assertTrue(String.format("Expected at least %d characters; only saw %d",
mMinBugreportBytes, data.length()), data.length() >= mMinBugreportBytes);
// TODO: check the captured report more extensively, perhaps using loganalysis
} finally {
- StreamUtil.cancel(bugreport);
StreamUtil.close(bugreportData);
}
}
@@ -366,10 +364,9 @@
while (!passed) {
// sleep a small amount of time to ensure last log message makes it into capture
RunUtil.getDefault().sleep(10);
- InputStreamSource source = getDevice().getLogcat(100 * 1024);
- assertNotNull(source);
File tmpTxtFile = FileUtil.createTempFile("logcat", ".txt");
- try {
+ try (InputStreamSource source = getDevice().getLogcat(100 * 1024)) {
+ assertNotNull(source);
FileUtil.writeToFile(source.createInputStream(), tmpTxtFile);
CLog.i("Created file at %s", tmpTxtFile.getAbsolutePath());
// ensure last log message is present in log
@@ -379,7 +376,6 @@
}
} finally {
FileUtil.deleteFile(tmpTxtFile);
- source.cancel();
}
retry++;
if ((retry > 100) && !passed) {
diff --git a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java
index 3f2ffa8..75ffc85 100644
--- a/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java
+++ b/hostsidetests/aadb/src/android/aadb/cts/TestDeviceStressTest.java
@@ -33,8 +33,8 @@
*/
public class TestDeviceStressTest extends DeviceTestCase {
- private static final int TEST_FILE_COUNT= 200;
- private int mIterations = 25;
+ private static final int TEST_FILE_COUNT= 100;
+ private int mIterations = 20;
private ITestDevice mTestDevice;
@Override
diff --git a/hostsidetests/abioverride/AndroidTest.xml b/hostsidetests/abioverride/AndroidTest.xml
index 18b0063..99f731c 100644
--- a/hostsidetests/abioverride/AndroidTest.xml
+++ b/hostsidetests/abioverride/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS AbiOverride host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="webview" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/abioverride/app/Android.mk b/hostsidetests/abioverride/app/Android.mk
index a246f72..9dd8d1f 100755
--- a/hostsidetests/abioverride/app/Android.mk
+++ b/hostsidetests/abioverride/app/Android.mk
@@ -27,7 +27,10 @@
LOCAL_MULTILIB := both
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+ ctstestrunner \
+
LOCAL_JNI_SHARED_LIBRARIES := libctsabioverride
diff --git a/hostsidetests/abioverride/app/AndroidManifest.xml b/hostsidetests/abioverride/app/AndroidManifest.xml
index c42d3a8..6135732 100755
--- a/hostsidetests/abioverride/app/AndroidManifest.xml
+++ b/hostsidetests/abioverride/app/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.abioverride.app">
<application android:use32bitAbi="true" android:multiArch="true">
+ <uses-library android:name="android.test.runner" />
+
<activity android:name=".AbiOverrideActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/appsecurity/AndroidTest.xml b/hostsidetests/appsecurity/AndroidTest.xml
index 6a9b557..2109a47 100644
--- a/hostsidetests/appsecurity/AndroidTest.xml
+++ b/hostsidetests/appsecurity/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS App Security host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="android.appsecurity.cts.AppSecurityPreparer" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-tampered-classes-dex.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-tampered-classes-dex.apk
new file mode 100644
index 0000000..07c356e
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-tampered-classes-dex.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index afd7245..aa7e4a0 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
@@ -23,13 +23,17 @@
import static android.appsecurity.cts.SplitTests.CLASS;
import static android.appsecurity.cts.SplitTests.PKG;
-import com.android.tradefed.build.IBuildInfo;
+import static org.junit.Assert.fail;
+
import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@@ -37,45 +41,57 @@
/**
* Set of tests that verify behavior of adopted storage media, if supported.
*/
-public class AdoptableHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
- private IAbi mAbi;
- private IBuildInfo mCtsBuild;
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AdoptableHostTest extends BaseHostJUnit4Test {
- @Override
- public void setAbi(IAbi abi) {
- mAbi = abi;
- }
+ public static final String FEATURE_ADOPTABLE_STORAGE = "feature:android.software.adoptable_storage";
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- Utils.prepareSingleUser(getDevice());
- assertNotNull(mAbi);
- assertNotNull(mCtsBuild);
+ @Before
+ public void setUp() throws Exception {
+ // Start all possible users to make sure their storage is unlocked
+ Utils.prepareMultipleUsers(getDevice(), Integer.MAX_VALUE);
getDevice().uninstallPackage(PKG);
+
+ // Enable a virtual disk to give us the best shot at being able to pass
+ // the various tests below. This helps verify devices that may not
+ // currently have an SD card inserted.
+ if (isSupportedDevice()) {
+ getDevice().executeShellCommand("sm set-virtual-disk true");
+ }
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
+ @After
+ public void tearDown() throws Exception {
getDevice().uninstallPackage(PKG);
+
+ if (isSupportedDevice()) {
+ getDevice().executeShellCommand("sm set-virtual-disk false");
+ }
}
+ /**
+ * Ensure that we have consistency between the feature flag and what we
+ * sniffed from the underlying fstab.
+ */
+ @Test
+ public void testFeatureConsistent() throws Exception {
+ final boolean hasFeature = hasFeature();
+ final boolean hasFstab = hasFstab();
+ if (hasFeature != hasFstab) {
+ fail("Inconsistent adoptable storage status; feature claims " + hasFeature
+ + " but fstab claims " + hasFstab);
+ }
+ }
+
+ @Test
public void testApps() throws Exception {
- if (!hasAdoptable()) return;
+ if (!isSupportedDevice()) return;
final String diskId = getAdoptionDisk();
try {
- final String abi = mAbi.getName();
+ final String abi = getAbi().getName();
final String apk = ABI_TO_APK.get(abi);
- assertNotNull("Failed to find APK for ABI " + abi, apk);
+ Assert.assertNotNull("Failed to find APK for ABI " + abi, apk);
// Install simple app on internal
new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
@@ -119,8 +135,9 @@
}
}
+ @Test
public void testPrimaryStorage() throws Exception {
- if (!hasAdoptable()) return;
+ if (!isSupportedDevice()) return;
final String diskId = getAdoptionDisk();
try {
final String originalVol = getDevice()
@@ -218,8 +235,9 @@
* Verify that we can install both new and inherited packages directly on
* adopted volumes.
*/
+ @Test
public void testPackageInstaller() throws Exception {
- if (!hasAdoptable()) return;
+ if (!isSupportedDevice()) return;
final String diskId = getAdoptionDisk();
try {
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -246,8 +264,9 @@
* Verify behavior when changes occur while adopted device is ejected and
* returned at a later time.
*/
+ @Test
public void testEjected() throws Exception {
- if (!hasAdoptable()) return;
+ if (!isSupportedDevice()) return;
final String diskId = getAdoptionDisk();
try {
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -291,7 +310,15 @@
}
}
- private boolean hasAdoptable() throws Exception {
+ private boolean isSupportedDevice() throws Exception {
+ return hasFeature() || hasFstab();
+ }
+
+ private boolean hasFeature() throws Exception {
+ return getDevice().hasFeature(FEATURE_ADOPTABLE_STORAGE);
+ }
+
+ private boolean hasFstab() throws Exception {
return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim());
}
@@ -335,11 +362,6 @@
getDevice().executeShellCommand("sm forget all");
}
- private void runDeviceTests(String packageName, String testClassName, String testMethodName)
- throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
- }
-
private static void assertSuccess(String str) {
if (str == null || !str.startsWith("Success")) {
throw new AssertionError("Expected success string but found " + str);
@@ -367,7 +389,7 @@
private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
public InstallMultiple() {
- super(getDevice(), mCtsBuild, mAbi);
+ super(getDevice(), getBuild(), getAbi());
}
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java
index 90b89af..797ffcb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityPreparer.java
@@ -29,7 +29,6 @@
import com.android.tradefed.targetprep.ITargetCleaner;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.util.StreamUtil;
/**
* Creates secondary and tertiary users for use during a test suite.
@@ -56,11 +55,8 @@
"Created secondary user " + device.createUser("CTS_" + System.nanoTime()));
}
} catch (IllegalStateException e) {
- InputStreamSource logcat = device.getLogcatDump();
- try {
+ try (InputStreamSource logcat = device.getLogcatDump()) {
mLogger.testLog("AppSecurityPrep_failed_create_user", LogDataType.LOGCAT, logcat);
- } finally {
- StreamUtil.cancel(logcat);
}
throw new TargetSetupError("Failed to create user.", e, device.getDeviceDescriptor());
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 00bac12..02e62d3 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -16,32 +16,31 @@
package android.appsecurity.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.AbiUtils;
-import com.android.tradefed.util.RunUtil;
-import java.io.BufferedReader;
-import java.io.EOFException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
/**
* Set of tests that verify various security checks involving multiple apps are
* properly enforced.
*/
-public class AppSecurityTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AppSecurityTests extends BaseHostJUnit4Test {
// testSharedUidDifferentCerts constants
private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
@@ -86,39 +85,22 @@
private static final String LOG_TAG = "AppSecurityTests";
- private IAbi mAbi;
- private IBuildInfo mCtsBuild;
-
- @Override
- public void setAbi(IAbi abi) {
- mAbi = abi;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
private File getTestAppFile(String fileName) throws FileNotFoundException {
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
return buildHelper.getTestFile(fileName);
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
Utils.prepareSingleUser(getDevice());
- assertNotNull(mCtsBuild);
+ assertNotNull(getBuild());
}
/**
* Test that an app that declares the same shared uid as an existing app, cannot be installed
* if it is signed with a different certificate.
*/
+ @Test
public void testSharedUidDifferentCerts() throws Exception {
Log.i(LOG_TAG, "installing apks with shared uid, but different certs");
try {
@@ -126,7 +108,7 @@
getDevice().uninstallPackage(SHARED_UI_PKG);
getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
String installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_APK),
false, options);
assertNull(String.format("failed to install shared uid app, Reason: %s", installResult),
@@ -147,13 +129,14 @@
* Test that an app update cannot be installed over an existing app if it has a different
* certificate.
*/
+ @Test
public void testAppUpgradeDifferentCerts() throws Exception {
Log.i(LOG_TAG, "installing app upgrade with different certs");
try {
// cleanup test app that might be installed from previous partial test run
getDevice().uninstallPackage(SIMPLE_APP_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
String installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_APK),
false, options);
assertNull(String.format("failed to install simple app. Reason: %s", installResult),
@@ -172,6 +155,7 @@
/**
* Test that an app cannot access another app's private data.
*/
+ @Test
public void testAppFailAccessPrivateData() throws Exception {
Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
try {
@@ -179,7 +163,7 @@
getDevice().uninstallPackage(APP_WITH_DATA_PKG);
getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
false, options);
assertNull(String.format("failed to install app with data. Reason: %s", installResult),
@@ -202,13 +186,14 @@
/**
* Test that uninstall of an app removes its private data.
*/
+ @Test
public void testUninstallRemovesData() throws Exception {
Log.i(LOG_TAG, "Uninstalling app, verifying data is removed.");
try {
// cleanup test app that might be installed from previous partial test run
getDevice().uninstallPackage(APP_WITH_DATA_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK),
false, options);
assertNull(String.format("failed to install app with data. Reason: %s", installResult),
@@ -233,6 +218,7 @@
/**
* Test that an app cannot instrument another app that is signed with different certificate.
*/
+ @Test
public void testInstrumentationDiffCert() throws Exception {
Log.i(LOG_TAG, "installing app that attempts to instrument another app");
try {
@@ -240,7 +226,7 @@
getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
String installResult = getDevice().installPackage(
getTestAppFile(TARGET_INSTRUMENT_APK), false, options);
assertNull(String.format("failed to install target instrumentation app. Reason: %s",
@@ -266,6 +252,7 @@
* Test that an app cannot use a signature-enforced permission if it is signed with a different
* certificate than the app that declared the permission.
*/
+ @Test
public void testPermissionDiffCert() throws Exception {
Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
try {
@@ -274,7 +261,7 @@
getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
String installResult = getDevice().installPackage(
getTestAppFile(DECLARE_PERMISSION_APK), false, options);
assertNull(String.format("failed to install declare permission app. Reason: %s",
@@ -302,18 +289,14 @@
/**
* Tests that an arbitrary file cannot be installed using the 'cmd' command.
*/
+ @Test
public void testAdbInstallFile() throws Exception {
String output = getDevice().executeShellCommand(
"cmd package install -S 1024 /data/local/tmp/foo.apk");
- assertEquals("Error text", "Error: APK content must be streamed\n", output);
+ assertTrue("Error text", output.contains("Error"));
}
private void runDeviceTests(String packageName) throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName);
- }
-
- private void runDeviceTests(String packageName, String testClassName, String testMethodName)
- throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+ runDeviceTests(packageName, null);
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
index 920e0a5..5e2a97b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseAppSecurityTest.java
@@ -17,18 +17,18 @@
package android.appsecurity.cts;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Assert;
+import org.junit.Before;
import java.util.ArrayList;
/**
* Base class.
*/
-public class BaseAppSecurityTest extends DeviceTestCase implements IBuildReceiver {
- protected IBuildInfo mBuildInfo;
+abstract class BaseAppSecurityTest extends BaseHostJUnit4Test {
/** Whether multi-user is supported. */
protected boolean mSupportsMultiUser;
@@ -37,15 +37,9 @@
/** Users we shouldn't delete in the tests */
private ArrayList<Integer> mFixedUsers;
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mBuildInfo = buildInfo;
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- assertNotNull(mBuildInfo); // ensure build has been set before test is run.
+ @Before
+ public void setUp() throws Exception {
+ Assert.assertNotNull(getBuild()); // ensure build has been set before test is run.
mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
mIsSplitSystemUser = checkIfSplitSystemUser();
@@ -70,8 +64,8 @@
if (userId < 0) {
userId = mPrimaryUserId;
}
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuildInfo);
- assertNull(getDevice().installPackageForUser(
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ Assert.assertNull(getDevice().installPackageForUser(
buildHelper.getTestFile(apk), true, false, userId, "-t"));
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
index e291771..f4f6d9e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ClassloaderSplitsTest.java
@@ -15,11 +15,17 @@
*/
package android.appsecurity.cts;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-public class ClassloaderSplitsTest extends DeviceTestCase implements IBuildReceiver {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ClassloaderSplitsTest extends BaseHostJUnit4Test implements IBuildReceiver {
private static final String PKG = "com.android.cts.classloadersplitapp";
private static final String TEST_CLASS = PKG + ".SplitAppTest";
@@ -39,54 +45,48 @@
private static final String APK_FEATURE_A = "CtsClassloaderSplitAppFeatureA.apk";
private static final String APK_FEATURE_B = "CtsClassloaderSplitAppFeatureB.apk";
- private IBuildInfo mBuildInfo;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
Utils.prepareSingleUser(getDevice());
getDevice().uninstallPackage(PKG);
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ @After
+ public void tearDown() throws Exception {
getDevice().uninstallPackage(PKG);
}
+ @Test
public void testBaseClassLoader() throws Exception {
new InstallMultiple().addApk(APK_BASE).run();
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
}
+ @Test
public void testFeatureAClassLoader() throws Exception {
new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).run();
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
}
+ @Test
public void testFeatureBClassLoader() throws Exception {
new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureBClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureAClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testFeatureBClassLoader");
}
+ @Test
public void testReceiverClassLoaders() throws Exception {
new InstallMultiple().addApk(APK_BASE).addApk(APK_FEATURE_A).addApk(APK_FEATURE_B).run();
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
- Utils.runDeviceTests(getDevice(), PKG, TEST_CLASS, "testAllReceivers");
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mBuildInfo = buildInfo;
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testBaseClassLoader");
+ runDeviceTests(getDevice(), PKG, TEST_CLASS, "testAllReceivers");
}
private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
public InstallMultiple() {
- super(getDevice(), mBuildInfo, null);
+ super(getDevice(), getBuild(), null);
}
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index 2174fa0..5408115 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -16,15 +16,20 @@
package android.appsecurity.cts;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.CollectingOutputReceiver;
import com.android.ddmlib.Log;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Set of tests that verify behavior of direct boot, if supported.
@@ -32,7 +37,8 @@
* Note that these tests drive PIN setup manually instead of relying on device
* administrators, which are not supported by all devices.
*/
-public class DirectBootHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class DirectBootHostTest extends BaseHostJUnit4Test {
private static final String TAG = "DirectBootHostTest";
private static final String PKG = "com.android.cts.encryptionapp";
@@ -47,43 +53,25 @@
private static final String MODE_EMULATED = "emulated";
private static final String MODE_NONE = "none";
- private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin\n";
- private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive\n";
+ private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin";
+ private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive";
private static final long SHUTDOWN_TIME_MS = 30 * 1000;
- private String mFeatureList = null;
-
private int[] mUsers;
- private IAbi mAbi;
- private IBuildInfo mCtsBuild;
- @Override
- public void setAbi(IAbi abi) {
- mAbi = abi;
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
mUsers = Utils.prepareSingleUser(getDevice());
- assertNotNull(mAbi);
- assertNotNull(mCtsBuild);
+ assertNotNull(getAbi());
+ assertNotNull(getBuild());
getDevice().uninstallPackage(PKG);
getDevice().uninstallPackage(OTHER_PKG);
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
+ @After
+ public void tearDown() throws Exception {
getDevice().uninstallPackage(PKG);
getDevice().uninstallPackage(OTHER_PKG);
}
@@ -91,6 +79,7 @@
/**
* Automotive devices MUST support native FBE.
*/
+ @Test
public void testAutomotiveNativeFbe() throws Exception {
if (!isSupportedDevice()) {
Log.v(TAG, "Device not supported; skipping test");
@@ -107,6 +96,7 @@
/**
* If device has native FBE, verify lifecycle.
*/
+ @Test
public void testDirectBootNative() throws Exception {
if (!isSupportedDevice()) {
Log.v(TAG, "Device not supported; skipping test");
@@ -122,6 +112,7 @@
/**
* If device doesn't have native FBE, enable emulation and verify lifecycle.
*/
+ @Test
public void testDirectBootEmulated() throws Exception {
if (!isSupportedDevice()) {
Log.v(TAG, "Device not supported; skipping test");
@@ -137,6 +128,7 @@
/**
* If device doesn't have native FBE, verify normal lifecycle.
*/
+ @Test
public void testDirectBootNone() throws Exception {
if (!isSupportedDevice()) {
Log.v(TAG, "Device not supported; skipping test");
@@ -213,7 +205,7 @@
int... users) throws DeviceNotAvailableException {
for (int user : users) {
Log.d(TAG, "runDeviceTests " + testMethodName + " u" + user);
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, user);
+ runDeviceTests(getDevice(), packageName, testClassName, testMethodName, user, null);
}
}
@@ -236,20 +228,12 @@
return "1".equals(output);
}
- private boolean hasSystemFeature(final String feature) throws Exception {
- if (mFeatureList == null) {
- mFeatureList = getDevice().executeShellCommand("pm list features");
- }
-
- return mFeatureList.contains(feature);
- }
-
private boolean isSupportedDevice() throws Exception {
- return hasSystemFeature(FEATURE_DEVICE_ADMIN);
+ return getDevice().hasFeature(FEATURE_DEVICE_ADMIN);
}
private boolean isAutomotiveDevice() throws Exception {
- return hasSystemFeature(FEATURE_AUTOMOTIVE);
+ return getDevice().hasFeature(FEATURE_AUTOMOTIVE);
}
private void waitForBootCompleted() throws Exception {
@@ -269,7 +253,7 @@
private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
public InstallMultiple() {
- super(getDevice(), mCtsBuild, mAbi);
+ super(getDevice(), getBuild(), getAbi());
}
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index 914f4ef..5564125 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -219,6 +219,7 @@
runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInstallPermissionGranted");
}
+ /** Test for android.permission.INSTANT_APP_FOREGROUND_SERVICE */
public void testStartForegrondService() throws Exception {
// Make sure the test package does not have INSTANT_APP_FOREGROUND_SERVICE
getDevice().executeShellCommand("cmd package revoke " + EPHEMERAL_1_PKG
@@ -226,6 +227,41 @@
runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testStartForegroundService");
}
+ /** Test for android.permission.RECORD_AUDIO */
+ public void testRecordAudioPermission() throws Exception {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testRecordAudioPermission");
+ }
+
+ /** Test for android.permission.CAMERA */
+ public void testCameraPermission() throws Exception {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testCameraPermission");
+ }
+
+ /** Test for android.permission.READ_PHONE_NUMBERS */
+ public void testReadPhoneNumbersPermission() throws Exception {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testReadPhoneNumbersPermission");
+ }
+
+ /** Test for android.permission.ACCESS_COARSE_LOCATION */
+ public void testAccessCoarseLocationPermission() throws Throwable {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testAccessCoarseLocationPermission");
+ }
+
+ /** Test for android.permission.NETWORK */
+ public void testInternetPermission() throws Throwable {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testInternetPermission");
+ }
+
+ /** Test for android.permission.VIBRATE */
+ public void testVibratePermission() throws Throwable {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testVibratePermission");
+ }
+
+ /** Test for android.permission.WAKE_LOCK */
+ public void testWakeLockPermission() throws Throwable {
+ runDeviceTests(EPHEMERAL_1_PKG, TEST_CLASS, "testWakeLockPermission");
+ }
+
private static final HashMap<String, String> makeArgs(
String action, String category, String mimeType) {
if (action == null || action.length() == 0) {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 87d8bd6..8cf385d 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -16,24 +16,32 @@
package android.appsecurity.cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.ddmlib.Log;
-import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.AbiUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
/**
* Set of tests that verify behavior of external storage devices.
*/
-public class ExternalStorageHostTest extends DeviceTestCase
- implements IAbiReceiver, IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ExternalStorageHostTest extends BaseHostJUnit4Test {
private static final String TAG = "ExternalStorageHostTest";
private static final String COMMON_CLASS =
@@ -53,47 +61,29 @@
private static final String MULTIUSER_CLASS = ".MultiUserStorageTest";
private int[] mUsers;
- private IAbi mAbi;
- private IBuildInfo mCtsBuild;
-
- @Override
- public void setAbi(IAbi abi) {
- mAbi = abi;
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
private File getTestAppFile(String fileName) throws FileNotFoundException {
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
return buildHelper.getTestFile(fileName);
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
+ @Before
+ public void setUp() throws Exception {
mUsers = Utils.prepareMultipleUsers(getDevice());
- assertNotNull(mAbi);
- assertNotNull(mCtsBuild);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ assertNotNull(getAbi());
+ assertNotNull(getBuild());
}
/**
* Verify that app with no external storage permissions works correctly.
*/
+ @Test
public void testExternalStorageNone() throws Exception {
try {
wipePrimaryExternalStorage();
getDevice().uninstallPackage(NONE_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
for (int user : mUsers) {
@@ -110,12 +100,13 @@
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
* correctly.
*/
+ @Test
public void testExternalStorageRead() throws Exception {
try {
wipePrimaryExternalStorage();
getDevice().uninstallPackage(READ_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
for (int user : mUsers) {
@@ -132,12 +123,13 @@
* {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
* correctly.
*/
+ @Test
public void testExternalStorageWrite() throws Exception {
try {
wipePrimaryExternalStorage();
getDevice().uninstallPackage(WRITE_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
for (int user : mUsers) {
@@ -153,6 +145,7 @@
* Verify that app with WRITE_EXTERNAL can leave gifts in external storage
* directories belonging to other apps, and those apps can read.
*/
+ @Test
public void testExternalStorageGifts() throws Exception {
try {
wipePrimaryExternalStorage();
@@ -160,7 +153,7 @@
getDevice().uninstallPackage(NONE_PKG);
getDevice().uninstallPackage(READ_PKG);
getDevice().uninstallPackage(WRITE_PKG);
- final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ final String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
// We purposefully delay the installation of the reading apps to
// verify that the daemon correctly invalidates any caches.
@@ -186,6 +179,7 @@
* Test multi-user emulated storage environment, ensuring that each user has
* isolated storage.
*/
+ @Test
public void testMultiUserStorageIsolated() throws Exception {
try {
if (mUsers.length == 1) {
@@ -198,7 +192,7 @@
// Install our test app
getDevice().uninstallPackage(MULTIUSER_PKG);
- String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
final String installResult = getDevice()
.installPackage(getTestAppFile(MULTIUSER_APK), false, options);
assertNull("Failed to install: " + installResult, installResult);
@@ -231,6 +225,7 @@
* Test that apps with read permissions see the appropriate permissions
* when apps with r/w permission levels move around their files.
*/
+ @Test
public void testMultiViewMoveConsistency() throws Exception {
try {
wipePrimaryExternalStorage();
@@ -238,7 +233,7 @@
getDevice().uninstallPackage(NONE_PKG);
getDevice().uninstallPackage(READ_PKG);
getDevice().uninstallPackage(WRITE_PKG);
- final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ final String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
@@ -271,13 +266,14 @@
}
/** Verify that app without READ_EXTERNAL can play default URIs in external storage. */
+ @Test
public void testExternalStorageReadDefaultUris() throws Exception {
try {
wipePrimaryExternalStorage();
getDevice().uninstallPackage(NONE_PKG);
getDevice().uninstallPackage(WRITE_PKG);
- final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+ final String[] options = {AbiUtils.createAbiFlag(getAbi().getName())};
assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
@@ -301,6 +297,45 @@
}
}
+ /**
+ * For security reasons, the shell user cannot access the shared storage of
+ * secondary users. Instead, developers should use the {@code content} shell
+ * tool to read/write files in those locations.
+ */
+ @Test
+ public void testSecondaryUsersInaccessible() throws Exception {
+ List<String> mounts = new ArrayList<>();
+ for (String line : getDevice().executeShellCommand("cat /proc/mount").split("\n")) {
+ String[] split = line.split(" ");
+ if (split[1].startsWith("/storage/") || split[1].startsWith("/mnt/")) {
+ mounts.add(split[1]);
+ }
+ }
+
+ for (int user : mUsers) {
+ String probe = "/sdcard/../" + user;
+ if (user == Utils.USER_SYSTEM) {
+ // Primary user should always be visible. Skip checking raw
+ // mount points, since we'd get false-positives for physical
+ // devices that aren't multi-user aware.
+ assertTrue(probe, access(probe));
+ } else {
+ // Secondary user should never be visible.
+ assertFalse(probe, access(probe));
+ for (String mount : mounts) {
+ probe = mount + "/" + user;
+ assertFalse(probe, access(probe));
+ }
+ }
+ }
+ }
+
+ private boolean access(String path) throws DeviceNotAvailableException {
+ final long nonce = System.nanoTime();
+ return getDevice().executeShellCommand("ls -la " + path + " && echo " + nonce)
+ .contains(Long.toString(nonce));
+ }
+
private void enableWriteSettings(String packageName, int userId)
throws DeviceNotAvailableException {
StringBuilder cmd = new StringBuilder();
@@ -324,11 +359,11 @@
private void runDeviceTests(String packageName, String testClassName, int userId)
throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, userId);
+ runDeviceTests(getDevice(), packageName, testClassName, null, userId, null);
}
private void runDeviceTests(String packageName, String testClassName, String testMethodName,
int userId) throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId);
+ runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, null);
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
new file mode 100644
index 0000000..15c3d3c
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/MajorVersionTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+/**
+ * Test that install of apps using major version codes is being handled properly.
+ */
+public class MajorVersionTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+ private static final String PKG = "com.android.cts.majorversion";
+ private static final String APK_000000000000ffff = "CtsMajorVersion000000000000ffff.apk";
+ private static final String APK_00000000ffffffff = "CtsMajorVersion00000000ffffffff.apk";
+ private static final String APK_000000ff00000000 = "CtsMajorVersion000000ff00000000.apk";
+ private static final String APK_000000ffffffffff = "CtsMajorVersion000000ffffffffff.apk";
+
+ private IAbi mAbi;
+ private CompatibilityBuildHelper mBuildHelper;
+
+ @Override
+ public void setAbi(IAbi abi) {
+ mAbi = abi;
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Utils.prepareSingleUser(getDevice());
+ assertNotNull(mAbi);
+ assertNotNull(mBuildHelper);
+
+ getDevice().uninstallPackage(PKG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ getDevice().uninstallPackage(PKG);
+ }
+
+ public void testInstallMinorVersion() throws Exception {
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000000000ffff), false, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ getDevice().uninstallPackage(PKG);
+ }
+
+ public void testInstallMajorVersion() throws Exception {
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000ff00000000), false, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ getDevice().uninstallPackage(PKG);
+ }
+
+ public void testInstallUpdateAcrossMinorMajorVersion() throws Exception {
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000000000ffff), false, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_00000000ffffffff), true, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000ff00000000), true, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000ffffffffff), true, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ getDevice().uninstallPackage(PKG);
+ }
+
+ public void testInstallDowngradeAcrossMajorMinorVersion() throws Exception {
+ assertNull(getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000ffffffffff), false, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_00000000ffffffff), true, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000ff00000000), true, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ assertEquals("INSTALL_FAILED_VERSION_DOWNGRADE", getDevice().installPackage(
+ mBuildHelper.getTestFile(APK_000000000000ffff), true, false));
+ assertTrue(getDevice().getInstalledPackageNames().contains(PKG));
+ runVersionDeviceTests("testCheckVersion");
+ getDevice().uninstallPackage(PKG);
+ }
+
+ private void runVersionDeviceTests(String testMethodName)
+ throws DeviceNotAvailableException {
+ runDeviceTests(PKG, PKG + ".VersionTest", testMethodName);
+ }
+
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+ throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+ }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
index 033f257..5bb70d1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
@@ -16,11 +16,21 @@
package android.appsecurity.cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Tests for visibility of packages installed in one user, in a different user.
*/
+@RunWith(DeviceJUnit4ClassRunner.class)
public class PackageVisibilityTest extends BaseAppSecurityTest {
private static final String TINY_APK = "CtsPkgInstallTinyApp.apk";
@@ -35,8 +45,8 @@
private int[] mUsers;
private String mOldVerifierValue;
- public void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUpPackage() throws Exception {
mUsers = Utils.prepareMultipleUsers(getDevice());
mOldVerifierValue =
@@ -47,14 +57,15 @@
installTestAppForUser(TEST_APK, mPrimaryUserId);
}
+ @After
public void tearDown() throws Exception {
getDevice().uninstallPackage(TEST_PKG);
getDevice().uninstallPackage(TINY_PKG);
getDevice().executeShellCommand("settings put global package_verifier_enable "
+ mOldVerifierValue);
- super.tearDown();
}
+ @Test
public void testUninstalledPackageVisibility() throws Exception {
if (!mSupportsMultiUser) {
return;
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
index caabeb5..28c44fc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
@@ -46,6 +46,10 @@
private static final String APK_ESCLATE_TO_RUNTIME_PERMISSIONS =
"CtsEscalateToRuntimePermissions.apk";
+ private static final String APK_ACCESS_SERIAL_LEGACY = "CtsAccessSerialLegacy.apk";
+ private static final String APK_ACCESS_SERIAL_MODERN = "CtsAccessSerialModern.apk";
+ private static final String ACCESS_SERIAL_PKG = "android.os.cts";
+
private static final String SCREEN_OFF_TIMEOUT_NS = "system";
private static final String SCREEN_OFF_TIMEOUT_KEY = "screen_off_timeout";
private String mScreenTimeoutBeforeTest;
@@ -74,6 +78,7 @@
getDevice().uninstallPackage(USES_PERMISSION_PKG);
getDevice().uninstallPackage(ESCALATE_PERMISSION_PKG);
getDevice().uninstallPackage(PERMISSION_POLICY_25_PKG);
+ getDevice().uninstallPackage(ACCESS_SERIAL_PKG);
// Set screen timeout to 30 min to not timeout while waiting for UI to change
mScreenTimeoutBeforeTest = getDevice().getSetting(SCREEN_OFF_TIMEOUT_NS,
@@ -95,6 +100,7 @@
getDevice().uninstallPackage(USES_PERMISSION_PKG);
getDevice().uninstallPackage(ESCALATE_PERMISSION_PKG);
getDevice().uninstallPackage(PERMISSION_POLICY_25_PKG);
+ getDevice().uninstallPackage(ACCESS_SERIAL_PKG);
}
public void testFail() throws Exception {
@@ -347,6 +353,30 @@
"testNoProtectionFlagsAddedToNonSignatureProtectionPermissions");
}
+ public void testLegacyAppAccessSerial() throws Exception {
+ assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
+ APK_PERMISSION_POLICY_25), false, false));
+ runDeviceTests(PERMISSION_POLICY_25_PKG,
+ "com.android.cts.permission.policy.PermissionPolicyTest25",
+ "testNoProtectionFlagsAddedToNonSignatureProtectionPermissions");
+ }
+
+ public void testSerialAccessPolicy() throws Exception {
+ // Verify legacy app behavior
+ assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
+ APK_ACCESS_SERIAL_LEGACY), false, false));
+ runDeviceTests(ACCESS_SERIAL_PKG,
+ "android.os.cts.AccessSerialLegacyTest",
+ "testAccessSerialNoPermissionNeeded");
+
+ // Verify modern app behavior
+ assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
+ APK_ACCESS_SERIAL_MODERN), true, false));
+ runDeviceTests(ACCESS_SERIAL_PKG,
+ "android.os.cts.AccessSerialModernTest",
+ "testAccessSerialPermissionNeeded");
+ }
+
private void runDeviceTests(String packageName, String testClassName, String testMethodName)
throws DeviceNotAvailableException {
Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 589d3b9..d64cfd4 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -234,6 +234,16 @@
);
}
+ public void testInstallV1SignatureOnlyDoesNotVerify() throws Exception {
+ // APK signed with v1 scheme only, but not all digests match those recorded in
+ // META-INF/MANIFEST.MF.
+ String error = "META-INF/MANIFEST.MF has invalid digest";
+
+ // Bitflip in classes.dex of otherwise good file.
+ assertInstallFailsWithError(
+ "v1-only-with-tampered-classes-dex.apk", error);
+ }
+
public void testInstallV2SignatureDoesNotVerify() throws Exception {
// APK signed with v2 scheme only, but the signature over signed-data does not verify.
String error = "signature did not verify";
@@ -469,9 +479,10 @@
}
public void testInstallEphemeralRequiresV2Signature() throws Exception {
- String expectedNoSigError = "No APK Signature Scheme v2 signature in ephemeral package";
- assertInstallEphemeralFailsWithError("unsigned-ephemeral.apk", expectedNoSigError);
- assertInstallEphemeralFailsWithError("v1-only-ephemeral.apk", expectedNoSigError);
+ assertInstallEphemeralFailsWithError("unsigned-ephemeral.apk",
+ "Failed to collect certificates");
+ assertInstallEphemeralFailsWithError("v1-only-ephemeral.apk",
+ "No APK Signature Scheme v2 signature");
assertInstallEphemeralSucceeds("v2-only-ephemeral.apk");
assertInstallEphemeralSucceeds("v1-v2-ephemeral.apk"); // signed with both schemes
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 9474ba8..acec1fb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -34,7 +34,7 @@
static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
static final String PKG = "com.android.cts.splitapp";
- static final String CLASS = ".SplitAppTest";
+ static final String CLASS = PKG + ".SplitAppTest";
static final String APK = "CtsSplitApp.apk";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index fa0120d..b31f91a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -16,9 +16,13 @@
package android.appsecurity.cts;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import junit.framework.AssertionFailedError;
@@ -27,13 +31,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.TimeUnit;
+import java.util.Map;
/**
* Tests that exercise various storage APIs.
*/
@RunWith(DeviceJUnit4ClassRunner.class)
-public class StorageHostTest extends CompatibilityHostTestBase {
+public class StorageHostTest extends BaseHostJUnit4Test {
private static final String PKG_STATS = "com.android.cts.storagestatsapp";
private static final String PKG_A = "com.android.cts.storageapp_a";
private static final String PKG_B = "com.android.cts.storageapp_b";
@@ -69,8 +73,8 @@
}
@Test
- public void testVerifyQuota() throws Exception {
- Utils.runDeviceTests(getDevice(), PKG_STATS, CLASS_STATS, "testVerifyQuota");
+ public void testVerify() throws Exception {
+ Utils.runDeviceTests(getDevice(), PKG_STATS, CLASS_STATS, "testVerify");
}
@Test
@@ -236,7 +240,22 @@
public void runDeviceTests(String packageName, String testClassName, String testMethodName,
int userId) throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, null,
- 20L, TimeUnit.MINUTES);
+ if (!runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId, 20 * 60 * 1000L)) {
+ TestRunResult res = getLastDeviceRunResults();
+ if (res != null) {
+ StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
+ for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+ res.getTestResults().entrySet()) {
+ if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ } else {
+ throw new AssertionFailedError("Error when running device tests.");
+ }
+ }
}
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
index 67ee091..8bad048 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
@@ -32,26 +32,6 @@
public class Utils {
public static final int USER_SYSTEM = 0;
- public static void runDeviceTests(ITestDevice device, String packageName)
- throws DeviceNotAvailableException {
- runDeviceTests(device, packageName, null, null, USER_SYSTEM, null);
- }
-
- public static void runDeviceTests(ITestDevice device, String packageName, int userId)
- throws DeviceNotAvailableException {
- runDeviceTests(device, packageName, null, null, userId, null);
- }
-
- public static void runDeviceTests(ITestDevice device, String packageName, String testClassName)
- throws DeviceNotAvailableException {
- runDeviceTests(device, packageName, testClassName, null, USER_SYSTEM, null);
- }
-
- public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
- int userId) throws DeviceNotAvailableException {
- runDeviceTests(device, packageName, testClassName, null, userId, null);
- }
-
public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
String testMethodName) throws DeviceNotAvailableException {
runDeviceTests(device, packageName, testClassName, testMethodName, USER_SYSTEM, null);
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
new file mode 100644
index 0000000..e4b7962
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAccessSerialLegacy
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/AndroidManifest.xml
new file mode 100644
index 0000000..6976c56
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.os.cts">
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27" />
+
+ <application/>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.os.cts" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/src/android/os/cts/AccessSerialLegacyTest.java b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/src/android/os/cts/AccessSerialLegacyTest.java
new file mode 100644
index 0000000..6dd97c3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/src/android/os/cts/AccessSerialLegacyTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.cts;
+
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.os.Build;
+import org.junit.Test;
+
+/**
+ * Test that legacy apps can access the device serial without the phone permission.
+ */
+public class AccessSerialLegacyTest {
+ @Test
+ public void testAccessSerialNoPermissionNeeded() throws Exception {
+ // Build.SERIAL should provide the device serial for legacy apps.
+ // We don't know the serial but know that it should not be the dummy
+ // value returned to unauthorized callers, so make sure not that value
+ assertTrue("Build.SERIAL must be visible to legacy apps",
+ !Build.UNKNOWN.equals(Build.SERIAL));
+
+ // We don't have the READ_PHONE_STATE permission, so this should throw
+ try {
+ Build.getSerial();
+ fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+ } catch (SecurityException e) {
+ /* expected */
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
new file mode 100644
index 0000000..20d51b6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+ android-support-test \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAccessSerialModern
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.xml
new file mode 100644
index 0000000..2eb5777
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.os.cts">
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.os.cts" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
new file mode 100644
index 0000000..df8ed97
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.cts;
+
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * Test that legacy apps can access the device serial without the phone permission.
+ */
+public class AccessSerialModernTest {
+ @Test
+ public void testAccessSerialPermissionNeeded() throws Exception {
+ // Build.SERIAL should not provide the device serial for modern apps.
+ // We don't know the serial but know that it should be the dummy
+ // value returned to unauthorized callers, so make sure that value
+ assertTrue("Build.SERIAL must not work for modern apps",
+ Build.UNKNOWN.equals(Build.SERIAL));
+
+ // We don't have the read phone state permission, so this should throw
+ try {
+ Build.getSerial();
+ fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+ } catch (SecurityException e) {
+ /* expected */
+ }
+
+ // Now grant ourselves READ_PHONE_STATE
+ grantReadPhoneStatePermission();
+
+ // Build.SERIAL should not provide the device serial for modern apps.
+ assertTrue("Build.SERIAL must not work for modern apps",
+ Build.UNKNOWN.equals(Build.SERIAL));
+
+ // We have the READ_PHONE_STATE permission, so this should not throw
+ try {
+ assertTrue("Build.getSerial() must work for apps holding READ_PHONE_STATE",
+ !Build.UNKNOWN.equals(Build.getSerial()));
+ } catch (SecurityException e) {
+ fail("getSerial() must be gated on the READ_PHONE_STATE permission");
+ }
+ }
+
+ private void grantReadPhoneStatePermission() throws IOException {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "pm grant " + InstrumentationRegistry.getContext().getPackageName()
+ + " " + Manifest.permission.READ_PHONE_STATE);
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
index 2e6911d..615492c 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.mk
@@ -21,7 +21,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_PACKAGE_NAME := CtsAppAccessData
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
index ffc104e..6a846fc 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/AndroidManifest.xml
@@ -22,6 +22,8 @@
created by com.android.cts.appwithdata.
-->
+ <uses-permission android:name="android.permission.INTERNET"/>
+
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
index 0867968..8c5b768 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/src/com/android/cts/appaccessdata/AccessPrivateDataTest.java
@@ -16,17 +16,25 @@
package com.android.cts.appaccessdata;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.test.AndroidTestCase;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
/**
* Test that another app's private data cannot be accessed, while its public data can.
@@ -51,6 +59,8 @@
*/
private static final String PUBLIC_FILE_NAME = "public_file.txt";
+ private static final Uri PRIVATE_TARGET = Uri.parse("content://com.android.cts.appwithdata/");
+
/**
* Tests that another app's private data cannot be accessed. It includes file
* and detailed traffic stats.
@@ -68,7 +78,6 @@
} catch (FileNotFoundException | SecurityException e) {
// expected
}
- accessPrivateTrafficStats();
}
private ApplicationInfo getApplicationInfo(String packageName) {
@@ -97,7 +106,7 @@
}
}
- private void accessPrivateTrafficStats() throws IOException {
+ public void testAccessPrivateTrafficStats() throws IOException {
int otherAppUid = -1;
try {
otherAppUid = getContext()
@@ -121,4 +130,46 @@
fail("Was not able to access qtaguid/stats: " + e);
}
}
+
+ public void testTrafficStatsStatsUidSelf() throws Exception {
+ final int uid = android.os.Process.myUid();
+ final long rxb = TrafficStats.getUidRxBytes(uid);
+ final long rxp = TrafficStats.getUidRxPackets(uid);
+ final long txb = TrafficStats.getUidTxBytes(uid);
+ final long txp = TrafficStats.getUidTxPackets(uid);
+
+ // Start remote server
+ final int port = getContext().getContentResolver().call(PRIVATE_TARGET, "start", null, null)
+ .getInt("port");
+
+ // Try talking to them, but shift blame
+ try {
+ final Socket socket = new Socket();
+ socket.setTcpNoDelay(true);
+
+ Bundle extras = new Bundle();
+ extras.putParcelable("fd", ParcelFileDescriptor.fromSocket(socket));
+ getContext().getContentResolver().call(PRIVATE_TARGET, "tag", null, extras);
+
+ socket.connect(new InetSocketAddress("localhost", port));
+
+ socket.getOutputStream().write(42);
+ socket.getOutputStream().flush();
+ final int val = socket.getInputStream().read();
+ assertEquals(42, val);
+ socket.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ getContext().getContentResolver().call(PRIVATE_TARGET, "stop", null, null);
+ }
+
+ SystemClock.sleep(1000);
+
+ // Since we shifted blame, our stats shouldn't have changed
+ assertEquals(rxb, TrafficStats.getUidRxBytes(uid));
+ assertEquals(rxp, TrafficStats.getUidRxPackets(uid));
+ assertEquals(txb, TrafficStats.getUidTxBytes(uid));
+ assertEquals(txp, TrafficStats.getUidTxPackets(uid));
+ }
}
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk b/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
index 3d11a83..c719665 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/Android.mk
@@ -21,7 +21,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_PACKAGE_NAME := CtsAppWithData
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
index 598ebe7..2accec1 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/AndroidManifest.xml
@@ -23,8 +23,14 @@
-->
<uses-permission android:name="android.permission.INTERNET" />
+
<application>
<uses-library android:name="android.test.runner" />
+
+ <provider
+ android:name="com.android.cts.appwithdata.MyProvider"
+ android:authorities="com.android.cts.appwithdata"
+ android:exported="true" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/MyProvider.java b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/MyProvider.java
new file mode 100644
index 0000000..135c27c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/src/com/android/cts/appwithdata/MyProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appwithdata;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class MyProvider extends ContentProvider {
+ private static final String TAG = "CTS";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ Log.v(TAG, "call() " + method);
+ switch (method) {
+ case "start": {
+ server = new EchoServer();
+ server.start();
+ extras = new Bundle();
+ extras.putInt("port", server.server.getLocalPort());
+ return extras;
+ }
+ case "stop": {
+ server.halt();
+ return null;
+ }
+ case "tag": {
+ Log.v(TAG, "My UID is " + android.os.Process.myUid());
+ TrafficStats.setThreadStatsUidSelf();
+ try {
+ final ParcelFileDescriptor pfd = extras.getParcelable("fd");
+ TrafficStats.tagFileDescriptor(pfd.getFileDescriptor());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ TrafficStats.clearThreadStatsUid();
+ }
+ return null;
+ }
+ default: {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private EchoServer server;
+
+ private static class EchoServer extends Thread {
+ final ServerSocket server;
+ volatile boolean halted = false;
+
+ public EchoServer() {
+ try {
+ server = new ServerSocket(0);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void halt() {
+ halted = true;
+ try {
+ server.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void run() {
+ while (!halted) {
+ try {
+ final Socket socket = server.accept();
+ socket.setTcpNoDelay(true);
+ final int val = socket.getInputStream().read();
+ socket.getOutputStream().write(val);
+ socket.getOutputStream().flush();
+ socket.close();
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ }
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk
index 0a55834..ad50a57 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.mk
@@ -22,6 +22,8 @@
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsDocumentClient
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
index 68e554d..7038685 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
@@ -60,6 +60,10 @@
public void setUp() throws Exception {
super.setUp();
+ // Wake up the device and dismiss the keyguard before the test starts.
+ executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ executeShellCommand("wm dismiss-keyguard");
+
Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
// Disable IME's to avoid virtual keyboards from showing up. Occasionally IME draws some UI
@@ -140,6 +144,14 @@
return true;
}
+ protected boolean supportedHardwareForScopedDirectoryAccess() {
+ final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+ if (pm.hasSystemFeature("android.hardware.type.watch")) {
+ return false;
+ }
+ return true;
+ }
+
protected void assertActivityFailed() {
final Result result = mActivity.getResult();
assertEquals(REQUEST_CODE, result.requestCode);
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
index dec8769..7277148 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
@@ -77,7 +77,7 @@
}
public void testInvalidPath() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
for (StorageVolume volume : getVolumes()) {
openExternalDirectoryInvalidPath(volume, "");
@@ -89,7 +89,7 @@
}
public void testUserRejects() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
for (StorageVolume volume : getVolumes()) {
// Tests user clicking DENY button, for all valid directories.
@@ -114,7 +114,7 @@
}
public void testUserAccepts() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
for (StorageVolume volume : getVolumes()) {
userAcceptsTest(volume, DIRECTORY_PICTURES);
@@ -125,7 +125,7 @@
}
public void testUserAcceptsNewDirectory() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
// TODO: figure out a better way to remove the directory.
final String command = "rm -rf /sdcard/" + DIRECTORY_PICTURES;
@@ -137,7 +137,7 @@
}
public void testNotAskedAgain() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
for (StorageVolume volume : getVolumes()) {
final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
@@ -157,7 +157,7 @@
}
public void testNotAskedAgainOnRoot() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
for (StorageVolume volume : getVolumes()) {
if (volume.isPrimary()) continue;
@@ -185,7 +185,7 @@
}
public void testDeniesOnceButAllowsAskingAgain() throws Exception {
- if (!supportedHardware())return;
+ if (!supportedHardwareForScopedDirectoryAccess())return;
final String[] dirs = { DIRECTORY_DCIM, DIRECTORY_ROOT };
for (StorageVolume volume : getVolumes()) {
@@ -210,7 +210,7 @@
}
public void testDeniesOnceForAll() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
final String[] dirs = {DIRECTORY_PICTURES, DIRECTORY_ROOT};
for (StorageVolume volume : getVolumes()) {
@@ -246,13 +246,13 @@
}
public void testRemovePackageStep1UserDenies() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
deniesOnceForAllTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
}
public void testRemovePackageStep2UserAcceptsDoNotClear() throws Exception {
- if (!supportedHardware()) return;
+ if (!supportedHardwareForScopedDirectoryAccess()) return;
userAcceptsTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
}
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk
index 7609e33..42aa074 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.mk
@@ -20,7 +20,12 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
index 894eff1..6e140d7 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.documentprovider">
<application>
+ <uses-library android:name="android.test.runner" />
+
<provider android:name=".MyDocumentsProvider"
android:authorities="com.android.cts.documentprovider"
android:exported="true"
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
index a4a9436..9734112 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.mk
@@ -22,6 +22,8 @@
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsEncryptionApp
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
index aaeb8c0..f011c80 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
@@ -17,12 +17,11 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-aia-util \
android-support-test \
- legacy-android-test \
ctsdeviceutillegacy \
ctstestrunner
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index 6028ae5..8f39344 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -21,8 +21,15 @@
android:minSdkVersion="25" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
- <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
+ <uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="@string/app_name">
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
index 656be27..0db23d4 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
@@ -16,17 +16,17 @@
package com.android.cts.ephemeralapp1;
+import static android.media.AudioFormat.CHANNEL_IN_MONO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+import static android.media.MediaRecorder.AudioSource.MIC;
import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertFalse;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
-import android.annotation.Nullable;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.ActivityNotFoundException;
@@ -41,14 +41,29 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.media.AudioRecord;
+import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestThread;
import com.android.cts.util.TestResult;
import org.junit.After;
@@ -85,6 +100,7 @@
"com.android.cts.ephemeraltest.EXTRA_ACTIVITY_RESULT";
private BroadcastReceiver mReceiver;
+ private PhoneStateListener mPhoneStateListener;
private final SynchronousQueue<TestResult> mResultQueue = new SynchronousQueue<>();
@Before
@@ -994,6 +1010,137 @@
latch2.await(5, TimeUnit.SECONDS);
}
+ @Test
+ public void testRecordAudioPermission() throws Throwable {
+ final AudioRecord record =
+ new AudioRecord(MIC, 8000, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, 4096);
+ try {
+ assertThat("audio record not initialized",
+ record.getState(), is(AudioRecord.STATE_INITIALIZED));
+ } finally {
+ record.release();
+ }
+ }
+
+ @Test
+ public void testReadPhoneNumbersPermission() throws Throwable {
+ final Context context = InstrumentationRegistry.getContext();
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ try {
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final String nmbr = telephonyManager.getLine1Number();
+ } catch (SecurityException e) {
+ fail("Permission not granted");
+ }
+ }
+
+ @Test
+ public void testAccessCoarseLocationPermission() throws Throwable {
+ final Context context = InstrumentationRegistry.getContext();
+ final ConnectivityManager mCm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+ return;
+ }
+
+ final TelephonyManager mTelephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ // getCellLocation should never return null, but that is allowed if the cell network type
+ // is LTE (since there is no LteCellLocation class)
+ if (mTelephonyManager.getNetworkType() != TelephonyManager.NETWORK_TYPE_LTE) {
+ assertThat(mTelephonyManager.getCellLocation(), is(notNullValue()));
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestThread t = new TestThread(() -> {
+ Looper.prepare();
+ mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCellLocationChanged(CellLocation location) {
+ latch.countDown();
+ }
+ };
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+ Looper.loop();
+ });
+ t.start();
+
+ try {
+ CellLocation.requestLocationUpdate();
+ assertThat(latch.await(1000, TimeUnit.MILLISECONDS), is(true));
+ } finally {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ }
+ t.checkException();
+ }
+
+ @Test
+ public void testCameraPermission() throws Throwable {
+ final Context context = InstrumentationRegistry.getContext();
+ final CameraManager manager =
+ (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ final String[] cameraIds = manager.getCameraIdList();
+ if (cameraIds.length == 0) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final HandlerThread backgroundThread = new HandlerThread("camera_bg");
+ backgroundThread.start();
+ final CameraDevice.StateCallback callback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ latch.countDown();
+ camera.close();
+ }
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ camera.close();
+ }
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ camera.close();
+ }
+ };
+ manager.openCamera(cameraIds[0], callback, new Handler(backgroundThread.getLooper()));
+ assertThat(latch.await(1000, TimeUnit.MILLISECONDS), is(true));
+ }
+
+ @Test
+ public void testInternetPermission() throws Throwable {
+ final ConnectivityManager manager = (ConnectivityManager) InstrumentationRegistry.getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ manager.reportNetworkConnectivity(null, false);
+ }
+
+ @Test
+ public void testVibratePermission() throws Throwable {
+ final Vibrator vibrator = (Vibrator) InstrumentationRegistry.getContext()
+ .getSystemService(Context.VIBRATOR_SERVICE);
+ final VibrationEffect effect =
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrator.vibrate(effect);
+ }
+
+ @Test
+ public void testWakeLockPermission() throws Throwable {
+ WakeLock wakeLock = null;
+ try {
+ final PowerManager powerManager = (PowerManager) InstrumentationRegistry.getContext()
+ .getSystemService(Context.POWER_SERVICE);
+ wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "test");
+ wakeLock.acquire();
+ }
+ finally {
+ if (wakeLock != null && wakeLock.isHeld()) {
+ wakeLock.release();
+ }
+ }
+ }
+
private TestResult getResult() {
final TestResult result;
try {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
index 31a45b0..6309704 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
@@ -20,8 +20,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-aia-util \
- android-support-test \
- legacy-android-test
+ android-support-test
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
index 43deb82..d9fa238 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
@@ -20,8 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-aia-util \
- android-support-test \
- legacy-android-test
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
index 1206e56..35c089f 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
@@ -20,8 +20,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-aia-util \
- android-support-test \
- legacy-android-test
+ android-support-test
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk
index f446140..cfa0b55 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.mk
@@ -19,8 +19,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- legacy-android-test
+ android-support-test
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
index 47d468e..2fcbc20 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
@@ -18,7 +18,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsExternalStorageApp
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
index a48abbf..b99597b 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.mk
@@ -21,7 +21,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_PACKAGE_NAME := CtsInstrumentationAppDiffCert
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.mk
index 0834642..3231710 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.mk
@@ -18,20 +18,25 @@
include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsIsolatedSplitApp
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+LOCAL_SDK_VERSION := current
+# Feature splits are dependent on this base, so it must be exported.
+LOCAL_EXPORT_PACKAGE_RESOURCES := true
+
+# Make sure our test locale polish is not stripped.
+LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := CtsIsolatedSplitApp
+# Generate a locale split.
LOCAL_PACKAGE_SPLITS := pl
-# Tag this module as a cts test artifact
-
include $(BUILD_CTS_SUPPORT_PACKAGE)
+# Build the other splits.
include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.mk b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.mk
index 48b4e3b..dd76592 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.mk
@@ -17,20 +17,32 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsIsolatedSplitAppFeatureA
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := tests
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# Feature splits are dependent on this split, so it must be exported.
LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_PACKAGE_NAME := CtsIsolatedSplitAppFeatureA
+
+# Make sure our test locale polish is not stripped.
+LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
LOCAL_SRC_FILES := $(call all-subdir-java-files)
+# Generate a locale split.
LOCAL_PACKAGE_SPLITS := pl
+# Code and resource dependency on the base.
LOCAL_APK_LIBRARIES := CtsIsolatedSplitApp
LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_AAPT_FLAGS += --custom-package com.android.cts.isolatedsplitapp.feature_a
+# Although feature splits use unique resource package names, they must all
+# have the same manifest package name to be considered one app.
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.cts.isolatedsplitapp
+
+# Assign a unique package ID to this feature split. Since these are isolated splits,
+# it must only be unique across a dependency chain.
LOCAL_AAPT_FLAGS += --package-id 0x80
include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
index d3aed1d..958b8d0 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
@@ -15,17 +15,17 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.isolatedsplitapp"
+ package="com.android.cts.isolatedsplitapp.feature_a"
featureSplit="feature_a">
<application>
- <activity android:name=".feature_a.FeatureAActivity">
+ <activity android:name=".FeatureAActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <receiver android:name=".feature_a.FeatureAReceiver">
+ <receiver android:name=".FeatureAReceiver">
<intent-filter>
<action android:name="com.android.cts.isolatedsplitapp.ACTION" />
</intent-filter>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.mk b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.mk
index 64b5fc3..240fc2c 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.mk
@@ -17,19 +17,29 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsIsolatedSplitAppFeatureB
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := tests
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_PACKAGE_NAME := CtsIsolatedSplitAppFeatureB
+
+# Make sure our test locale polish is not stripped.
+LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
LOCAL_SRC_FILES := $(call all-subdir-java-files)
+# Generate a locale split.
LOCAL_PACKAGE_SPLITS := pl
+# Code and resource dependency on the base and feature A.
LOCAL_APK_LIBRARIES := CtsIsolatedSplitApp CtsIsolatedSplitAppFeatureA
LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_AAPT_FLAGS := --custom-package com.android.cts.isolatedsplitapp.feature_b
+# Although feature splits use unique resource package names, they must all
+# have the same manifest package name to be considered one app.
+LOCAL_AAPT_FLAGS := --rename-manifest-package com.android.cts.isolatedsplitapp
+
+# Assign a unique package ID to this feature split. Since these are isolated splits,
+# it must only be unique across a dependency chain.
LOCAL_AAPT_FLAGS += --package-id 0x81
include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
index 00c2d6c..d89a1f2 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
@@ -15,19 +15,19 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.isolatedsplitapp"
+ package="com.android.cts.isolatedsplitapp.feature_b"
featureSplit="feature_b">
<uses-split android:name="feature_a" />
<application>
- <activity android:name=".feature_b.FeatureBActivity">
+ <activity android:name=".FeatureBActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <receiver android:name=".feature_b.FeatureBReceiver">
+ <receiver android:name=".FeatureBReceiver">
<intent-filter>
<action android:name="com.android.cts.isolatedsplitapp.ACTION" />
</intent-filter>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.mk b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.mk
index f21d1d0..35b3252 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.mk
@@ -17,19 +17,29 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsIsolatedSplitAppFeatureC
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := tests
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_PACKAGE_NAME := CtsIsolatedSplitAppFeatureC
+
+# Make sure our test locale polish is not stripped.
+LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
LOCAL_SRC_FILES := $(call all-subdir-java-files)
+# Generate a locale split.
LOCAL_PACKAGE_SPLITS := pl
+# Code and resource dependency on the base.
LOCAL_APK_LIBRARIES := CtsIsolatedSplitApp
LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_AAPT_FLAGS := --custom-package com.android.cts.isolatedsplitapp.feature_c
-LOCAL_AAPT_FLAGS += --package-id 0x82
+# Although feature splits use unique resource package names, they must all
+# have the same manifest package name to be considered one app.
+LOCAL_AAPT_FLAGS := --rename-manifest-package com.android.cts.isolatedsplitapp
+
+# Use the same package ID as feature A, since this is an isolated split and
+# will not be loaded together with feature A.
+LOCAL_AAPT_FLAGS += --package-id 0x80
include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
index ac3a57f..64b087c 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
@@ -15,17 +15,17 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.isolatedsplitapp"
+ package="com.android.cts.isolatedsplitapp.feature_c"
featureSplit="feature_c">
<application>
- <activity android:name=".feature_c.FeatureCActivity">
+ <activity android:name=".FeatureCActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <receiver android:name=".feature_c.FeatureCReceiver">
+ <receiver android:name=".FeatureCReceiver">
<intent-filter>
<action android:name="com.android.cts.isolatedsplitapp.ACTION" />
</intent-filter>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
index 2f6af13..b85e21b 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
@@ -45,15 +45,20 @@
@RunWith(AndroidJUnit4.class)
public class SplitAppTest {
private static final String PACKAGE = "com.android.cts.isolatedsplitapp";
+
private static final ComponentName FEATURE_A_ACTIVITY =
ComponentName.createRelative(PACKAGE, ".feature_a.FeatureAActivity");
private static final ComponentName FEATURE_B_ACTIVITY =
ComponentName.createRelative(PACKAGE, ".feature_b.FeatureBActivity");
private static final ComponentName FEATURE_C_ACTIVITY =
ComponentName.createRelative(PACKAGE, ".feature_c.FeatureCActivity");
- private static final String FEATURE_A_STRING = PACKAGE + ":string/feature_a_string";
- private static final String FEATURE_B_STRING = PACKAGE + ":string/feature_b_string";
- private static final String FEATURE_C_STRING = PACKAGE + ":string/feature_c_string";
+
+ private static final String FEATURE_A_STRING =
+ "com.android.cts.isolatedsplitapp.feature_a:string/feature_a_string";
+ private static final String FEATURE_B_STRING =
+ "com.android.cts.isolatedsplitapp.feature_b:string/feature_b_string";
+ private static final String FEATURE_C_STRING =
+ "com.android.cts.isolatedsplitapp.feature_c:string/feature_c_string";
private static final Configuration PL = new Configuration();
static {
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Android.mk
new file mode 100644
index 0000000..6047ca5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Android.mk
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.mk
new file mode 100644
index 0000000..db83bb0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion000000000000ffff
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
new file mode 100644
index 0000000..2a63cd7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.majorversion"
+ android:versionCodeMajor="0x00000000" android:versionCode="0x0000ffff">
+
+ <application/>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..4fe9fed
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.majorversion;
+
+public class VersionConstants {
+ public static final long PACKAGE_VERSION = 0x000000000000ffffL;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.mk
new file mode 100644
index 0000000..dd32f59
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion00000000ffffffff
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
new file mode 100644
index 0000000..934deec
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.majorversion"
+ android:versionCodeMajor="0x00000000" android:versionCode="0xffffffff">
+
+ <application/>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..4c9a62b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.majorversion;
+
+public class VersionConstants {
+ public static final long PACKAGE_VERSION = 0x00000000ffffffffL;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.mk
new file mode 100644
index 0000000..d534b7b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion000000ff00000000
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
new file mode 100644
index 0000000..c7b9dd0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.majorversion"
+ android:versionCodeMajor="0x000000ff" android:versionCode="0x00000000">
+
+ <application/>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..6a10139
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.majorversion;
+
+public class VersionConstants {
+ public static final long PACKAGE_VERSION = 0x000000ff00000000L;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.mk b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.mk
new file mode 100644
index 0000000..72c9914
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src-common) $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsMajorVersion000000ffffffffff
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# sign this app with a different cert than CtsSimpleAppInstallDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
new file mode 100644
index 0000000..91d4e39
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.majorversion"
+ android:versionCodeMajor="0x000000ff" android:versionCode="0xffffffff">
+
+ <application/>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.majorversion" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/src/com/android/cts/majorversion/VersionConstants.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/src/com/android/cts/majorversion/VersionConstants.java
new file mode 100644
index 0000000..dead996
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/src/com/android/cts/majorversion/VersionConstants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.majorversion;
+
+public class VersionConstants {
+ public static final long PACKAGE_VERSION = 0x000000ffffffffffL;
+}
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/com/android/cts/majorversion/VersionTest.java b/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/com/android/cts/majorversion/VersionTest.java
new file mode 100644
index 0000000..e69e14b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/com/android/cts/majorversion/VersionTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.majorversion;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+public class VersionTest {
+ @Test
+ public void testCheckVersion() throws Exception {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(InstrumentationRegistry.getContext().getPackageName(),
+ 0);
+ assertEquals("com.android.cts.majorversion",
+ InstrumentationRegistry.getContext().getPackageName());
+ assertEquals(VersionConstants.PACKAGE_VERSION, pi.getLongVersionCode());
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
index 0744834..e08ac6f 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.mk
@@ -18,7 +18,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
index 9206b6f..0d53ac8 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
@@ -25,8 +25,6 @@
compatibility-device-util \
ctstestrunner \
-LOCAL_JAVA_LIBRARIES := legacy-android-test
-
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsPermissionPolicyTest25
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
index e86fae9..5a584c1 100644
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
@@ -24,6 +24,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
index 4f828ad..51699dd 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
@@ -18,7 +18,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index 9341949..7c642b1 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -48,7 +50,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -75,7 +79,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -101,7 +107,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
index 9d0dec6..23e4c9f 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
@@ -16,6 +16,8 @@
package com.android.cts.storageapp;
+import static android.os.storage.StorageManager.UUID_DEFAULT;
+
import static com.android.cts.storageapp.Utils.CACHE_ALL;
import static com.android.cts.storageapp.Utils.CACHE_EXT;
import static com.android.cts.storageapp.Utils.CACHE_INT;
@@ -26,7 +28,6 @@
import static com.android.cts.storageapp.Utils.assertMostlyEquals;
import static com.android.cts.storageapp.Utils.getSizeManual;
import static com.android.cts.storageapp.Utils.makeUniqueFile;
-import static com.android.cts.storageapp.Utils.shouldHaveQuota;
import static com.android.cts.storageapp.Utils.useSpace;
import android.app.usage.StorageStats;
@@ -39,7 +40,6 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.os.storage.StorageManager;
-import android.system.Os;
import android.test.InstrumentationTestCase;
import java.io.File;
@@ -59,17 +59,10 @@
}
public void testFullDisk() throws Exception {
- if (shouldHaveQuota(Os.uname())) {
+ final StorageStatsManager stats = getContext()
+ .getSystemService(StorageStatsManager.class);
+ if (stats.isReservedSupported(UUID_DEFAULT)) {
final File dataDir = getContext().getDataDir();
-
- // Pre-flight to see if we have enough disk space to test with
- final long total = dataDir.getTotalSpace();
- final long free = dataDir.getFreeSpace();
- final long required = ((total * 9) / 10) + MB_IN_BYTES;
- if (free < required) {
- fail("Skipping full disk test; only found " + free + " free out of " + total);
- }
-
Hoarder.doBlocks(dataDir, true);
} else {
fail("Skipping full disk test due to missing quota support");
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
index 6908ad8..08dc7ad 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
@@ -169,30 +169,6 @@
return success;
}
- public static boolean shouldHaveQuota(StructUtsname uname) throws Exception {
- try (BufferedReader br = new BufferedReader(new FileReader("/proc/mounts"))) {
- String line;
- while ((line = br.readLine()) != null) {
- final String[] fields = line.split(" ");
- final String target = fields[1];
- final String format = fields[2];
-
- if (target.equals("/data") && !format.equals("ext4")) {
- Log.d(TAG, "Assuming no quota support because /data is " + format);
- return false;
- }
- }
- }
-
- final Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)").matcher(uname.release);
- if (!matcher.find()) {
- throw new IllegalStateException("Failed to parse version: " + uname.release);
- }
- final int major = Integer.parseInt(matcher.group(1));
- final int minor = Integer.parseInt(matcher.group(2));
- return (major > 3 || (major == 3 && minor >= 18));
- }
-
public static void logCommand(String... cmd) throws Exception {
final Process proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk b/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
index f3d7d35..ebbc892 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageAppA/Android.mk
@@ -17,8 +17,10 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_SDK_VERSION := test_current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, ../StorageApp/src/)
diff --git a/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk b/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
index 5f85459..3a5462e 100644
--- a/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageAppB/Android.mk
@@ -17,8 +17,10 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_SDK_VERSION := test_current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, ../StorageApp/src/)
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk b/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk
index b5c30fb..16d78d7 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.mk
@@ -18,7 +18,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := test_current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, ../StorageApp/src)
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
index 713ba17..351c493 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
@@ -30,7 +30,6 @@
import static com.android.cts.storageapp.Utils.getSizeManual;
import static com.android.cts.storageapp.Utils.logCommand;
import static com.android.cts.storageapp.Utils.makeUniqueFile;
-import static com.android.cts.storageapp.Utils.shouldHaveQuota;
import static com.android.cts.storageapp.Utils.useFallocate;
import static com.android.cts.storageapp.Utils.useSpace;
import static com.android.cts.storageapp.Utils.useWrite;
@@ -46,13 +45,12 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.support.test.uiautomator.UiDevice;
-import android.system.Os;
-import android.system.StructUtsname;
import android.test.InstrumentationTestCase;
import android.util.Log;
import android.util.MutableLong;
@@ -76,19 +74,22 @@
}
/**
- * Require that quota support be fully enabled on kernel 3.18 or newer. This
- * test verifies that both kernel options and the fstab 'quota' option are
- * enabled.
+ * Require that quota support be fully enabled on devices that first ship
+ * with P. This test verifies that both kernel options and the fstab 'quota'
+ * option are enabled.
*/
- public void testVerifyQuota() throws Exception {
- final StructUtsname uname = Os.uname();
- if (shouldHaveQuota(uname)) {
+ public void testVerify() throws Exception {
+ if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.P) {
final StorageStatsManager stats = getContext()
.getSystemService(StorageStatsManager.class);
- assertTrue("You're running kernel 3.18 or newer (" + uname.release + ") which "
- + "means that CONFIG_QUOTA, CONFIG_QFMT_V2, CONFIG_QUOTACTL and the "
- + "'quota' fstab option on /data are required",
+ assertTrue("Devices that first ship with P or newer must enable quotas to "
+ + "support StorageStatsManager APIs. You may need to enable the "
+ + "CONFIG_QUOTA, CONFIG_QFMT_V2, CONFIG_QUOTACTL kernel options "
+ + "and add the 'quota' fstab option on /data.",
stats.isQuotaSupported(UUID_DEFAULT));
+ assertTrue("Devices that first ship with P or newer must enable resgid to "
+ + "preserve system stability in the face of abusive apps.",
+ stats.isReservedSupported(UUID_DEFAULT));
}
}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
index 2b6c2b0..c98dbb1 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.mk
@@ -25,7 +25,7 @@
ctstestrunner \
ub-uiautomator
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java \
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
index 305359c..22d4a5f 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.mk
@@ -25,7 +25,7 @@
ctstestrunner \
ub-uiautomator
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml
index 71eadaa..9d25826 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/AndroidManifest.xml
@@ -66,6 +66,8 @@
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
index ac4f272..7edb1d1 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
@@ -25,6 +25,8 @@
ctstestrunner \
ub-uiautomator
+LOCAL_JAVA_LIBRARIES := android.test.base
+
LOCAL_SRC_FILES := $(call all-java-files-under, ../UsePermissionApp23/src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
LOCAL_RESOURCE_DIR := cts/hostsidetests/appsecurity/test-apps/UsePermissionApp23/res
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml
index acaeeb0..c6a6316 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/AndroidManifest.xml
@@ -67,6 +67,8 @@
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml
index 845e43d..9458db3 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/AndroidManifest.xml
@@ -24,6 +24,8 @@
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
index cac6790..57a58ab 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/AndroidManifest.xml
@@ -23,6 +23,8 @@
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="com.android.cts.usepermission.BasePermissionActivity" />
</application>
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
index 498e8ca..4605cfb 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
@@ -22,7 +22,9 @@
../PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_PACKAGE_NAME := CtsUsePermissionDiffCert
diff --git a/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk b/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk
index 2d67d62..df12f82 100644
--- a/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsesLibraryApp/Android.mk
@@ -22,6 +22,8 @@
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
index 8574d63..b1e84cb 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
@@ -20,8 +20,9 @@
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
- compatibility-device-util \
- legacy-android-test
+ compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
diff --git a/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk
index 907ae36..619f1ce 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.mk
@@ -22,7 +22,8 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_PACKAGE_NAME := CtsKeySetTestApp
LOCAL_DEX_PREOPT := false
diff --git a/hostsidetests/atrace/AndroidTest.xml b/hostsidetests/atrace/AndroidTest.xml
index 8262232..ecaa17a 100644
--- a/hostsidetests/atrace/AndroidTest.xml
+++ b/hostsidetests/atrace/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS atrace host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsAtraceHostTestCases.jar" />
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 314a92e3..868e20f 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Backup host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="backup" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/backup/SuccessNotificationApp/Android.mk b/hostsidetests/backup/SuccessNotificationApp/Android.mk
new file mode 100644
index 0000000..501cf14
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsBackupSuccessNotificationApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
new file mode 100644
index 0000000..b7f8c2c
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.cts.backup.successnotificationapp">
+
+ <application>
+ <receiver android:name=".SuccessNotificationReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BACKUP_FINISHED" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.cts.backup.successnotificationapp" />
+</manifest>
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java
new file mode 100644
index 0000000..2122151
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationReceiver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.successnotificationapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class SuccessNotificationReceiver extends BroadcastReceiver {
+ private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
+ private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!BACKUP_FINISHED_ACTION.equals(intent.getAction())) {
+ return;
+ }
+ final String packageName = intent.getStringExtra(BACKUP_FINISHED_PACKAGE_EXTRA);
+ if (packageName == null || packageName.isEmpty()) {
+ return;
+ }
+ context.getSharedPreferences(SuccessNotificationTest.PREFS_FILE, Context.MODE_PRIVATE)
+ .edit().putBoolean(packageName, true).commit();
+ }
+}
diff --git a/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
new file mode 100644
index 0000000..3fb8ef2
--- /dev/null
+++ b/hostsidetests/backup/SuccessNotificationApp/src/android/cts/backup/successnotificationapp/SuccessNotificationTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.successnotificationapp;
+
+import static android.support.test.InstrumentationRegistry.getTargetContext;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SuccessNotificationTest {
+ protected static final String PREFS_FILE = "android.cts.backup.successnotificationapp.PREFS";
+ private static final String KEY_VALUE_RESTORE_APP_PACKAGE =
+ "android.cts.backup.keyvaluerestoreapp";
+ private static final String FULLBACKUPONLY_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
+
+ @Test
+ public void clearBackupSuccessNotificationsReceived() {
+ getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE).edit().clear()
+ .commit();
+ }
+
+ @Test
+ public void verifyBackupSuccessNotificationReceivedForKeyValueApp() {
+ assertTrue(getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
+ .getBoolean(KEY_VALUE_RESTORE_APP_PACKAGE, false));
+ }
+
+ @Test
+ public void verifyBackupSuccessNotificationReceivedForFullBackupApp() {
+ assertTrue(getTargetContext().getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
+ .getBoolean(FULLBACKUPONLY_APP_PACKAGE, false));
+ }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
index 4f46936..73373d3 100644
--- a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
@@ -16,7 +16,7 @@
package android.cts.backup;
-import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNull;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
@@ -62,6 +62,7 @@
private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
@After
+ @Override
public void tearDown() throws Exception {
super.tearDown();
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
index acdb423..f0861c0 100644
--- a/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BaseBackupHostSideTest.java
@@ -16,15 +16,13 @@
package android.cts.backup;
-import static junit.framework.Assert.assertTrue;
-
import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Before;
@@ -38,7 +36,7 @@
* Base class for CTS backup/restore hostside tests
*/
@RunWith(DeviceJUnit4ClassRunner.class)
-public abstract class BaseBackupHostSideTest extends CompatibilityHostTestBase {
+public abstract class BaseBackupHostSideTest extends BaseHostJUnit4Test {
protected boolean mIsBackupSupported;
/** Value of PackageManager.FEATURE_BACKUP */
@@ -49,7 +47,7 @@
@Before
public void setUp() throws DeviceNotAvailableException, Exception {
- mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
+ mIsBackupSupported = getDevice().hasFeature("feature:" + FEATURE_BACKUP);
if (!mIsBackupSupported) {
CLog.i("android.software.backup feature is not supported on this device");
return;
@@ -71,7 +69,7 @@
* Execute shell command "bmgr backupnow <packageName>" and return output from this command.
*/
protected String backupNow(String packageName) throws DeviceNotAvailableException {
- return mDevice.executeShellCommand("bmgr backupnow " + packageName);
+ return getDevice().executeShellCommand("bmgr backupnow " + packageName);
}
/**
@@ -96,14 +94,14 @@
* Execute shell command "bmgr restore <packageName>" and return output from this command.
*/
protected String restore(String packageName) throws DeviceNotAvailableException {
- return mDevice.executeShellCommand("bmgr restore " + packageName);
+ return getDevice().executeShellCommand("bmgr restore " + packageName);
}
/**
* Attempts to clear the device log.
*/
protected void clearLogcat() throws DeviceNotAvailableException {
- mDevice.executeAdbCommand("logcat", "-c");
+ getDevice().executeAdbCommand("logcat", "-c");
}
/**
@@ -174,7 +172,7 @@
protected void startActivityInPackageAndWait(String packageName, String className)
throws DeviceNotAvailableException {
- mDevice.executeShellCommand(String.format(
+ getDevice().executeShellCommand(String.format(
"am start -W -a android.intent.action.MAIN -n %s/%s.%s", packageName,
packageName,
className));
@@ -187,19 +185,19 @@
*/
protected void clearBackupDataInLocalTransport(String packageName)
throws DeviceNotAvailableException {
- mDevice.executeShellCommand(String.format("bmgr wipe %s %s", LOCAL_TRANSPORT, packageName));
+ getDevice().executeShellCommand(String.format("bmgr wipe %s %s", LOCAL_TRANSPORT, packageName));
}
/**
* Clears package data
*/
protected void clearPackageData(String packageName) throws DeviceNotAvailableException {
- mDevice.executeShellCommand(String.format("pm clear %s", packageName));
+ getDevice().executeShellCommand(String.format("pm clear %s", packageName));
}
private boolean isBackupEnabled() throws DeviceNotAvailableException {
boolean isEnabled;
- String output = mDevice.executeShellCommand("bmgr enabled");
+ String output = getDevice().executeShellCommand("bmgr enabled");
Pattern pattern = Pattern.compile("^Backup Manager currently (enabled|disabled)$");
Matcher matcher = pattern.matcher(output.trim());
if (matcher.find()) {
@@ -211,7 +209,7 @@
}
private String getCurrentTransport() throws DeviceNotAvailableException {
- String output = mDevice.executeShellCommand("bmgr list transports");
+ String output = getDevice().executeShellCommand("bmgr list transports");
Pattern pattern = Pattern.compile("\\* (.*)");
Matcher matcher = pattern.matcher(output);
if (matcher.find()) {
diff --git a/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
index 89cfe40..6589993 100644
--- a/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/FullBackupOnlyHostSideTest.java
@@ -16,7 +16,7 @@
package android.cts.backup;
-import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNull;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
@@ -76,6 +76,7 @@
@After
+ @Override
public void tearDown() throws Exception {
super.tearDown();
diff --git a/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
index 8dd589a..e137e3e 100644
--- a/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/FullbackupRulesHostSideTest.java
@@ -16,8 +16,6 @@
package android.cts.backup;
-import static org.junit.Assert.assertTrue;
-
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
diff --git a/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
index a1f4927..5ce86d9 100644
--- a/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/KeyValueBackupRestoreHostSideTest.java
@@ -16,7 +16,7 @@
package android.cts.backup;
-import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNull;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
@@ -27,8 +27,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.FileNotFoundException;
-
/**
* Test for checking that key/value backup and restore works correctly.
* It interacts with the app that saves values in different shared preferences and files.
@@ -60,6 +58,7 @@
@Before
+ @Override
public void setUp() throws Exception {
super.setUp();
installPackage(KEY_VALUE_RESTORE_APP_APK);
@@ -70,6 +69,7 @@
}
@After
+ @Override
public void tearDown() throws Exception {
super.tearDown();
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
index 7c1f37b..a62eab8 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreAnyVersionHostSideTest.java
@@ -16,23 +16,17 @@
package android.cts.backup;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertNull;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.FileNotFoundException;
-
/**
* Test checking that restoreAnyVersion manifest flag is respected by backup manager.
*
@@ -62,6 +56,7 @@
"CtsBackupRestoreAnyVersionNoRestoreApp.apk";
@After
+ @Override
public void tearDown() throws Exception {
super.tearDown();
@@ -157,21 +152,21 @@
}
private void installRestoreAnyVersionApp()
- throws DeviceNotAvailableException, FileNotFoundException {
+ throws DeviceNotAvailableException, TargetSetupError {
installPackage(RESTORE_ANY_VERSION_APP_APK, "-d", "-r");
checkRestoreAnyVersionDeviceTest("checkAppVersionIsOld");
}
private void installNoRestoreAnyVersionApp()
- throws DeviceNotAvailableException, FileNotFoundException {
+ throws DeviceNotAvailableException, TargetSetupError {
installPackage(NO_RESTORE_ANY_VERSION_APK, "-d", "-r");
checkRestoreAnyVersionDeviceTest("checkAppVersionIsOld");
}
private void installNewVersionApp()
- throws DeviceNotAvailableException, FileNotFoundException {
+ throws DeviceNotAvailableException, TargetSetupError {
installPackage(RESTORE_ANY_VERSION_UPDATE_APK, "-d", "-r");
checkRestoreAnyVersionDeviceTest("checkAppVersionIsNew");
diff --git a/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
new file mode 100644
index 0000000..414e3bc
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/SuccessNotificationHostSideTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for checking that an observer app is notified by a broadcast Intent whenever a backup
+ * succeeds.
+ *
+ * NB: The tests use "bmgr backupnow" for backup, which works on N+ devices.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SuccessNotificationHostSideTest extends BaseBackupHostSideTest {
+
+ /** The name of the package that a key/value backup will be run for */
+ private static final String KEY_VALUE_BACKUP_APP_PACKAGE =
+ "android.cts.backup.keyvaluerestoreapp";
+
+ /** The name of the package that a full backup will be run for */
+ private static final String FULL_BACKUP_APP_PACKAGE = "android.cts.backup.fullbackuponlyapp";
+
+ /** The name of the package that observes backup results. */
+ private static final String SUCCESS_NOTIFICATION_APP_PACKAGE =
+ "android.cts.backup.successnotificationapp";
+
+ /** The name of the device side test class in the APK that a key/value backup will be run for */
+ private static final String KEY_VALUE_BACKUP_DEVICE_TEST_NAME =
+ KEY_VALUE_BACKUP_APP_PACKAGE + ".KeyValueBackupRestoreTest";
+
+ /** The name of the device side test class in the APK that a full backup will be run for */
+ private static final String FULL_BACKUP_DEVICE_TEST_CLASS_NAME =
+ FULL_BACKUP_APP_PACKAGE + ".FullBackupOnlyTest";
+
+ /** The name of the device side test class in the APK that observes backup results */
+ private static final String SUCCESS_NOTIFICATION_DEVICE_TEST_NAME =
+ SUCCESS_NOTIFICATION_APP_PACKAGE + ".SuccessNotificationTest";
+
+ /** The name of the APK that a key/value backup will be run for */
+ private static final String KEY_VALUE_BACKUP_APP_APK = "CtsKeyValueBackupRestoreApp.apk";
+
+ /** The name of the APK that a full backup will be run for */
+ private static final String FULL_BACKUP_APP_APK = "FullBackupOnlyFalseWithAgentApp.apk";
+
+ /** The name of the APK that observes backup results */
+ private static final String SUCCESS_NOTIFICATION_APP_APK =
+ "CtsBackupSuccessNotificationApp.apk";
+
+ /** Secure setting that holds the backup manager configuration as key-value pairs */
+ private static final String BACKUP_MANAGER_CONSTANTS_PREF = "backup_manager_constants";
+
+ /** Key for specifying the apps that the backup manager should notify of successful backups */
+ private static final String BACKUP_FINISHED_NOTIFICATION_RECEIVERS =
+ "backup_finished_notification_receivers";
+
+ /** The original backup manager configuration */
+ private String mOriginalBackupManagerConstants = null;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ if (!mIsBackupSupported) {
+ CLog.i("android.software.backup feature is not supported on this device");
+ return;
+ }
+
+ installPackage(KEY_VALUE_BACKUP_APP_APK);
+ installPackage(FULL_BACKUP_APP_APK);
+
+ installPackage(SUCCESS_NOTIFICATION_APP_APK);
+ checkDeviceTest("clearBackupSuccessNotificationsReceived");
+ addBackupFinishedNotificationReceiver();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ if (!mIsBackupSupported) {
+ return;
+ }
+
+ restoreBackupFinishedNotificationReceivers();
+ assertNull(uninstallPackage(SUCCESS_NOTIFICATION_APP_PACKAGE));
+
+ // Clear backup data and uninstall the package (in that order!)
+ clearBackupDataInLocalTransport(KEY_VALUE_BACKUP_APP_PACKAGE);
+ assertNull(uninstallPackage(KEY_VALUE_BACKUP_APP_PACKAGE));
+
+ clearBackupDataInLocalTransport(FULL_BACKUP_APP_PACKAGE);
+ assertNull(uninstallPackage(FULL_BACKUP_APP_PACKAGE));
+ }
+
+ /**
+ * Test that the observer app is notified when a key/value backup succeeds.
+ *
+ * Test logic:
+ * 1. Change a test app's data, trigger a key/value backup and wait for it to complete.
+ * 2. Verify that the observer app was informed about the backup.
+ */
+ @Test
+ public void testSuccessNotificationForKeyValueBackup() throws Exception {
+ if (!mIsBackupSupported) {
+ return;
+ }
+
+ checkDeviceTest(KEY_VALUE_BACKUP_APP_PACKAGE, KEY_VALUE_BACKUP_DEVICE_TEST_NAME,
+ "saveSharedPreferencesAndNotifyBackupManager");
+ backupNowAndAssertSuccess(KEY_VALUE_BACKUP_APP_PACKAGE);
+
+ checkDeviceTest("verifyBackupSuccessNotificationReceivedForKeyValueApp");
+ }
+
+ /**
+ * Test that the observer app is notified when a full backup succeeds.
+ *
+ * Test logic:
+ * 1. Change a test app's data, trigger a full backup and wait for it to complete.
+ * 2. Verify that the observer app was informed about the backup.
+ */
+ @Test
+ public void testSuccessNotificationForFullBackup() throws Exception {
+ if (!mIsBackupSupported) {
+ return;
+ }
+
+ checkDeviceTest(FULL_BACKUP_APP_PACKAGE, FULL_BACKUP_DEVICE_TEST_CLASS_NAME, "createFiles");
+ backupNowAndAssertSuccess(FULL_BACKUP_APP_PACKAGE);
+
+ checkDeviceTest("verifyBackupSuccessNotificationReceivedForFullBackupApp");
+ }
+
+ /**
+ * Instructs the backup manager to notify the observer app whenever a backup succeeds. The old
+ * backup manager configuration is stored in a member variable and can be restored by calling
+ * {@link restoreBackupFinishedNotificationReceivers}.
+ */
+ private void addBackupFinishedNotificationReceiver()
+ throws DeviceNotAvailableException {
+ mOriginalBackupManagerConstants = getDevice().executeShellCommand(String.format(
+ "settings get secure %s", BACKUP_MANAGER_CONSTANTS_PREF)).trim();
+ if ("null".equals(mOriginalBackupManagerConstants)) {
+ mOriginalBackupManagerConstants = null;
+ }
+ String backupManagerConstants = null;
+
+ if (mOriginalBackupManagerConstants == null || mOriginalBackupManagerConstants.isEmpty()) {
+ backupManagerConstants =
+ BACKUP_FINISHED_NOTIFICATION_RECEIVERS + "=" + SUCCESS_NOTIFICATION_APP_PACKAGE;
+ } else {
+ final List<String> keyValuePairs =
+ new ArrayList<>(Arrays.asList(mOriginalBackupManagerConstants.split(",")));
+ boolean present = false;
+ for (int i = 0; i < keyValuePairs.size(); ++i) {
+ final String keyValuePair = keyValuePairs.get(i);
+ final String[] fields = keyValuePair.split("=");
+ final String key = fields[0].trim();
+ if (BACKUP_FINISHED_NOTIFICATION_RECEIVERS.equals(key)) {
+ if (fields.length == 1 || fields[1].trim().isEmpty()) {
+ keyValuePairs.set(i, key + "=" + SUCCESS_NOTIFICATION_APP_PACKAGE);
+ } else {
+ final String[] values = fields[1].split(":");
+ for (int j = 0; j < values.length; ++j) {
+ if (SUCCESS_NOTIFICATION_APP_PACKAGE.equals(values[j].trim())) {
+ present = true;
+ break;
+ }
+ }
+ if (!present) {
+ keyValuePairs.set(i,
+ keyValuePair + ":" + SUCCESS_NOTIFICATION_APP_PACKAGE);
+ }
+ }
+ present = true;
+ break;
+ }
+ }
+ if (!present) {
+ keyValuePairs.add(BACKUP_FINISHED_NOTIFICATION_RECEIVERS + "=" +
+ SUCCESS_NOTIFICATION_APP_PACKAGE);
+ }
+ backupManagerConstants = String.join(",", keyValuePairs);
+ }
+ setBackupManagerConstants(backupManagerConstants);
+ }
+
+ /**
+ * Restores the backup manager configuration stored by a previous call to
+ * {@link addBackupFinishedNotificationReceiver}.
+ */
+ private void restoreBackupFinishedNotificationReceivers() throws DeviceNotAvailableException {
+ setBackupManagerConstants(mOriginalBackupManagerConstants);
+ }
+
+ private void setBackupManagerConstants(String backupManagerConstants)
+ throws DeviceNotAvailableException {
+ getDevice().executeShellCommand(String.format("settings put secure %s %s",
+ BACKUP_MANAGER_CONSTANTS_PREF, backupManagerConstants));
+ }
+
+ private void checkDeviceTest(String methodName) throws DeviceNotAvailableException {
+ checkDeviceTest(SUCCESS_NOTIFICATION_APP_PACKAGE, SUCCESS_NOTIFICATION_DEVICE_TEST_NAME,
+ methodName);
+ }
+}
diff --git a/hostsidetests/bootstats/AndroidTest.xml b/hostsidetests/bootstats/AndroidTest.xml
index 6d1be7d..5cac548 100644
--- a/hostsidetests/bootstats/AndroidTest.xml
+++ b/hostsidetests/bootstats/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS Boot Stats host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="sysui" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsBootStatsTestCases.jar" />
diff --git a/hostsidetests/compilation/AndroidTest.xml b/hostsidetests/compilation/AndroidTest.xml
index 9cc9066..63d53e9 100644
--- a/hostsidetests/compilation/AndroidTest.xml
+++ b/hostsidetests/compilation/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Compilation Test">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsCompilationTestCases.jar" />
diff --git a/hostsidetests/compilation/assets/primary.prof.txt b/hostsidetests/compilation/assets/primary.prof.txt
index 0a8bded..fd55262 100644
--- a/hostsidetests/compilation/assets/primary.prof.txt
+++ b/hostsidetests/compilation/assets/primary.prof.txt
@@ -1,103 +1,103 @@
Landroid/cts/compilation/CompilationTargetActivity;
-Landroid/cts/compilation/CompilationTargetActivity;->m0()I
-Landroid/cts/compilation/CompilationTargetActivity;->m1()I
-Landroid/cts/compilation/CompilationTargetActivity;->m2()I
-Landroid/cts/compilation/CompilationTargetActivity;->m3()I
-Landroid/cts/compilation/CompilationTargetActivity;->m4()I
-Landroid/cts/compilation/CompilationTargetActivity;->m5()I
-Landroid/cts/compilation/CompilationTargetActivity;->m6()I
-Landroid/cts/compilation/CompilationTargetActivity;->m7()I
-Landroid/cts/compilation/CompilationTargetActivity;->m8()I
-Landroid/cts/compilation/CompilationTargetActivity;->m9()I
-Landroid/cts/compilation/CompilationTargetActivity;->m10()I
-Landroid/cts/compilation/CompilationTargetActivity;->m11()I
-Landroid/cts/compilation/CompilationTargetActivity;->m12()I
-Landroid/cts/compilation/CompilationTargetActivity;->m13()I
-Landroid/cts/compilation/CompilationTargetActivity;->m14()I
-Landroid/cts/compilation/CompilationTargetActivity;->m15()I
-Landroid/cts/compilation/CompilationTargetActivity;->m16()I
-Landroid/cts/compilation/CompilationTargetActivity;->m17()I
-Landroid/cts/compilation/CompilationTargetActivity;->m18()I
-Landroid/cts/compilation/CompilationTargetActivity;->m19()I
-Landroid/cts/compilation/CompilationTargetActivity;->m20()I
-Landroid/cts/compilation/CompilationTargetActivity;->m21()I
-Landroid/cts/compilation/CompilationTargetActivity;->m22()I
-Landroid/cts/compilation/CompilationTargetActivity;->m23()I
-Landroid/cts/compilation/CompilationTargetActivity;->m24()I
-Landroid/cts/compilation/CompilationTargetActivity;->m25()I
-Landroid/cts/compilation/CompilationTargetActivity;->m26()I
-Landroid/cts/compilation/CompilationTargetActivity;->m27()I
-Landroid/cts/compilation/CompilationTargetActivity;->m28()I
-Landroid/cts/compilation/CompilationTargetActivity;->m29()I
-Landroid/cts/compilation/CompilationTargetActivity;->m30()I
-Landroid/cts/compilation/CompilationTargetActivity;->m31()I
-Landroid/cts/compilation/CompilationTargetActivity;->m32()I
-Landroid/cts/compilation/CompilationTargetActivity;->m33()I
-Landroid/cts/compilation/CompilationTargetActivity;->m34()I
-Landroid/cts/compilation/CompilationTargetActivity;->m35()I
-Landroid/cts/compilation/CompilationTargetActivity;->m36()I
-Landroid/cts/compilation/CompilationTargetActivity;->m37()I
-Landroid/cts/compilation/CompilationTargetActivity;->m38()I
-Landroid/cts/compilation/CompilationTargetActivity;->m39()I
-Landroid/cts/compilation/CompilationTargetActivity;->m40()I
-Landroid/cts/compilation/CompilationTargetActivity;->m41()I
-Landroid/cts/compilation/CompilationTargetActivity;->m42()I
-Landroid/cts/compilation/CompilationTargetActivity;->m43()I
-Landroid/cts/compilation/CompilationTargetActivity;->m44()I
-Landroid/cts/compilation/CompilationTargetActivity;->m45()I
-Landroid/cts/compilation/CompilationTargetActivity;->m46()I
-Landroid/cts/compilation/CompilationTargetActivity;->m47()I
-Landroid/cts/compilation/CompilationTargetActivity;->m48()I
-Landroid/cts/compilation/CompilationTargetActivity;->m49()I
-Landroid/cts/compilation/CompilationTargetActivity;->m50()I
-Landroid/cts/compilation/CompilationTargetActivity;->m51()I
-Landroid/cts/compilation/CompilationTargetActivity;->m52()I
-Landroid/cts/compilation/CompilationTargetActivity;->m53()I
-Landroid/cts/compilation/CompilationTargetActivity;->m54()I
-Landroid/cts/compilation/CompilationTargetActivity;->m55()I
-Landroid/cts/compilation/CompilationTargetActivity;->m56()I
-Landroid/cts/compilation/CompilationTargetActivity;->m57()I
-Landroid/cts/compilation/CompilationTargetActivity;->m58()I
-Landroid/cts/compilation/CompilationTargetActivity;->m59()I
-Landroid/cts/compilation/CompilationTargetActivity;->m60()I
-Landroid/cts/compilation/CompilationTargetActivity;->m61()I
-Landroid/cts/compilation/CompilationTargetActivity;->m62()I
-Landroid/cts/compilation/CompilationTargetActivity;->m63()I
-Landroid/cts/compilation/CompilationTargetActivity;->m64()I
-Landroid/cts/compilation/CompilationTargetActivity;->m65()I
-Landroid/cts/compilation/CompilationTargetActivity;->m66()I
-Landroid/cts/compilation/CompilationTargetActivity;->m67()I
-Landroid/cts/compilation/CompilationTargetActivity;->m68()I
-Landroid/cts/compilation/CompilationTargetActivity;->m69()I
-Landroid/cts/compilation/CompilationTargetActivity;->m70()I
-Landroid/cts/compilation/CompilationTargetActivity;->m71()I
-Landroid/cts/compilation/CompilationTargetActivity;->m72()I
-Landroid/cts/compilation/CompilationTargetActivity;->m73()I
-Landroid/cts/compilation/CompilationTargetActivity;->m74()I
-Landroid/cts/compilation/CompilationTargetActivity;->m75()I
-Landroid/cts/compilation/CompilationTargetActivity;->m76()I
-Landroid/cts/compilation/CompilationTargetActivity;->m77()I
-Landroid/cts/compilation/CompilationTargetActivity;->m78()I
-Landroid/cts/compilation/CompilationTargetActivity;->m79()I
-Landroid/cts/compilation/CompilationTargetActivity;->m80()I
-Landroid/cts/compilation/CompilationTargetActivity;->m81()I
-Landroid/cts/compilation/CompilationTargetActivity;->m82()I
-Landroid/cts/compilation/CompilationTargetActivity;->m83()I
-Landroid/cts/compilation/CompilationTargetActivity;->m84()I
-Landroid/cts/compilation/CompilationTargetActivity;->m85()I
-Landroid/cts/compilation/CompilationTargetActivity;->m86()I
-Landroid/cts/compilation/CompilationTargetActivity;->m87()I
-Landroid/cts/compilation/CompilationTargetActivity;->m88()I
-Landroid/cts/compilation/CompilationTargetActivity;->m89()I
-Landroid/cts/compilation/CompilationTargetActivity;->m90()I
-Landroid/cts/compilation/CompilationTargetActivity;->m91()I
-Landroid/cts/compilation/CompilationTargetActivity;->m92()I
-Landroid/cts/compilation/CompilationTargetActivity;->m93()I
-Landroid/cts/compilation/CompilationTargetActivity;->m94()I
-Landroid/cts/compilation/CompilationTargetActivity;->m95()I
-Landroid/cts/compilation/CompilationTargetActivity;->m96()I
-Landroid/cts/compilation/CompilationTargetActivity;->m97()I
-Landroid/cts/compilation/CompilationTargetActivity;->m98()I
-Landroid/cts/compilation/CompilationTargetActivity;->m99()I
-Landroid/cts/compilation/CompilationTargetActivity;->m100()I
-Landroid/cts/compilation/CompilationTargetActivity;->m101()I
\ No newline at end of file
+SHLandroid/cts/compilation/CompilationTargetActivity;->m0()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m1()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m2()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m3()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m4()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m5()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m6()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m7()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m8()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m9()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m10()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m11()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m12()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m13()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m14()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m15()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m16()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m17()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m18()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m19()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m20()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m21()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m22()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m23()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m24()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m25()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m26()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m27()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m28()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m29()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m30()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m31()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m32()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m33()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m34()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m35()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m36()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m37()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m38()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m39()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m40()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m41()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m42()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m43()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m44()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m45()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m46()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m47()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m48()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m49()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m50()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m51()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m52()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m53()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m54()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m55()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m56()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m57()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m58()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m59()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m60()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m61()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m62()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m63()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m64()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m65()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m66()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m67()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m68()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m69()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m70()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m71()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m72()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m73()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m74()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m75()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m76()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m77()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m78()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m79()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m80()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m81()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m82()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m83()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m84()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m85()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m86()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m87()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m88()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m89()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m90()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m91()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m92()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m93()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m94()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m95()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m96()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m97()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m98()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m99()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m100()I
+SHLandroid/cts/compilation/CompilationTargetActivity;->m101()I
\ No newline at end of file
diff --git a/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java b/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java
index 1f5d669..591dcb4 100644
--- a/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java
+++ b/hostsidetests/compilation/src/android/cts/compilation/AdbRootDependentCompilationTest.java
@@ -274,19 +274,15 @@
executePush(apkFile.getAbsolutePath(), targetPathApk, targetDir);
assertTrue("Failed to push APK from ", doesFileExist(targetPathApk));
// Run profman to create the real profile on device.
- try {
- String pathSpec = executeSuShellAdbCommand(1, "pm", "path", APPLICATION_PACKAGE)[0];
- pathSpec = pathSpec.replace("package:", "");
- assertTrue("Failed find APK " + pathSpec, doesFileExist(pathSpec));
- executeSuShellAdbCommand(
+ String pathSpec = executeSuShellAdbCommand(1, "pm", "path", APPLICATION_PACKAGE)[0];
+ pathSpec = pathSpec.replace("package:", "");
+ assertTrue("Failed find APK " + pathSpec, doesFileExist(pathSpec));
+ executeSuShellAdbCommand(
"profman",
"--create-profile-from=" + targetPathTemp,
"--apk=" + pathSpec,
"--dex-location=" + pathSpec,
"--reference-profile-file=" + targetPath);
- } catch (Exception e) {
- assertEquals("", e.toString());
- }
executeSuShellAdbCommand(0, "chown", owner, targetPath);
// Verify that the file was written successfully
assertTrue("failed to create profile file", doesFileExist(targetPath));
diff --git a/hostsidetests/content/AndroidTest.xml b/hostsidetests/content/AndroidTest.xml
index accf2a5..badbda7 100644
--- a/hostsidetests/content/AndroidTest.xml
+++ b/hostsidetests/content/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS Content host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsSyncContentHostTestCases.jar" />
diff --git a/hostsidetests/content/src/android/content/cts/SyncAdapterAccountAccessHostTest.java b/hostsidetests/content/src/android/content/cts/SyncAdapterAccountAccessHostTest.java
deleted file mode 100644
index f6f9d10..0000000
--- a/hostsidetests/content/src/android/content/cts/SyncAdapterAccountAccessHostTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.cts;
-
-import android.appsecurity.cts.Utils;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-/**
- * Set of tests that verify behavior of the content framework.
- */
-public class SyncAdapterAccountAccessHostTest extends DeviceTestCase
- implements IAbiReceiver, IBuildReceiver {
- private static final String ACCOUNT_ACCESS_TESTS_OTHER_CERT_APK =
- "CtsSyncAccountAccessOtherCertTestCases.apk";
- private static final String ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG =
- "com.android.cts.content";
-
- private static final String ACCOUNT_ACCESS_TESTS_SAME_CERT_APK =
- "CtsSyncAccountAccessSameCertTestCases.apk";
- private static final String ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG =
- "com.android.cts.content";
-
- private static final String STUBS_APK =
- "CtsSyncAccountAccessStubs.apk";
- private static final String STUBS_PKG =
- "com.android.cts.stub";
-
- private IAbi mAbi;
- private CompatibilityBuildHelper mBuildHelper;
-
- @Override
- public void setAbi(IAbi abi) {
- mAbi = abi;
- }
-
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mBuildHelper = new CompatibilityBuildHelper(buildInfo);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- getDevice().uninstallPackage(STUBS_PKG);
-
- assertNull(getDevice().installPackage(mBuildHelper
- .getTestFile(STUBS_APK), false, false));
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- getDevice().uninstallPackage(STUBS_PKG);
- }
-
- public void testSameCertAuthenticatorCanSeeAccount() throws Exception {
- getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG);
-
- assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
- ACCOUNT_ACCESS_TESTS_SAME_CERT_APK), false, false));
- try {
- runDeviceTests(ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG,
- "com.android.cts.content.CtsSyncAccountAccessSameCertTestCases",
- "testAccountAccess_sameCertAsAuthenticatorCanSeeAccount");
- } finally {
- getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_SAME_CERT_PKG);
- }
- }
-
- public void testOtherCertAuthenticatorCanSeeAccount() throws Exception {
- getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG);
-
- assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
- ACCOUNT_ACCESS_TESTS_OTHER_CERT_APK), false, false));
- try {
- runDeviceTests(ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG,
- "com.android.cts.content.CtsSyncAccountAccessOtherCertTestCases",
- "testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount");
- } finally {
- getDevice().uninstallPackage(ACCOUNT_ACCESS_TESTS_OTHER_CERT_PKG);
- }
- }
-
- private void runDeviceTests(String packageName, String testClassName, String testMethodName)
- throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
- }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk b/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk
deleted file mode 100644
index 116da94..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- ctstestrunner \
- ub-uiautomator \
- compatibility-device-util
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
- ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/StubActivity.java \
- ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java \
- ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java \
- ../CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java
-
-LOCAL_PACKAGE_NAME := CtsSyncAccountAccessOtherCertTestCases
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
deleted file mode 100644
index c2557ef..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SyncRequest;
-import android.content.SyncResult;
-import android.content.cts.FlakyTestRule;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-/**
- * Tests whether a sync adapter can access accounts.
- */
-@RunWith(AndroidJUnit4.class)
-public class CtsSyncAccountAccessOtherCertTestCases {
- private static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
- private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec
-
- private static final String PERMISSION_REQUESTED = "Permission Requested";
- public static final String TOKEN_TYPE_REMOVE_ACCOUNTS = "TOKEN_TYPE_REMOVE_ACCOUNTS";
-
- @Rule
- public final TestRule mFlakyTestRule = new FlakyTestRule(3);
-
- @Before
- public void setUp() throws Exception {
- allowSyncAdapterRunInBackgroundAndDataInBackground();
- }
-
- @After
- public void tearDown() throws Exception {
- disallowSyncAdapterRunInBackgroundAndDataInBackground();
- }
-
- @Test
- public void testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount() throws Exception {
- if (!hasDataConnection() || !hasNotificationSupport()) {
- return;
- }
-
- Intent intent = new Intent(getContext(), StubActivity.class);
- Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-
- AccountManager accountManager = getContext().getSystemService(AccountManager.class);
- Bundle result = accountManager.addAccount("com.stub", null, null, null, activity,
- null, null).getResult();
-
- Account addedAccount = new Account(
- result.getString(AccountManager.KEY_ACCOUNT_NAME),
- result.getString(AccountManager.KEY_ACCOUNT_TYPE));
-
- waitForSyncManagerAccountChangeUpdate();
-
- try {
- CountDownLatch latch = new CountDownLatch(1);
-
- SyncAdapter.setOnPerformSyncDelegate((Account account, Bundle extras,
- String authority, ContentProviderClient provider, SyncResult syncResult)
- -> latch.countDown());
-
- Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
- SyncRequest request = new SyncRequest.Builder()
- .setSyncAdapter(null, "com.android.cts.stub.provider")
- .syncOnce()
- .setExtras(extras)
- .setExpedited(true)
- .setManual(true)
- .build();
- ContentResolver.requestSync(request);
-
- assertFalse(latch.await(SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-
- UiDevice uiDevice = getUiDevice();
- if (isWatch()) {
- UiObject2 notification = findPermissionNotificationInStream(uiDevice);
- notification.click();
- } else {
- uiDevice.openNotification();
- uiDevice.wait(Until.hasObject(By.text(PERMISSION_REQUESTED)),
- UI_TIMEOUT_MILLIS);
-
- uiDevice.findObject(By.text(PERMISSION_REQUESTED)).click();
- }
-
- uiDevice.wait(Until.hasObject(By.text("ALLOW")),
- UI_TIMEOUT_MILLIS);
-
- uiDevice.findObject(By.text("ALLOW")).click();
-
- ContentResolver.requestSync(request);
-
- assertTrue(latch.await(SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
- } finally {
- // Ask the differently signed authenticator to drop all accounts
- accountManager.getAuthToken(addedAccount, TOKEN_TYPE_REMOVE_ACCOUNTS,
- null, false, null, null);
- activity.finish();
- }
- }
-
- private UiObject2 findPermissionNotificationInStream(UiDevice uiDevice) {
- uiDevice.pressHome();
- swipeUp(uiDevice);
- if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
- return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
- }
- for (int i = 0; i < 100; i++) {
- if (!swipeUp(uiDevice)) {
- // We have reached the end of the stream and not found the target.
- break;
- }
- if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
- return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
- }
- }
- return null;
- }
-
- private boolean swipeUp(UiDevice uiDevice) {
- int width = uiDevice.getDisplayWidth();
- int height = uiDevice.getDisplayHeight();
- return uiDevice.swipe(
- width / 2 /* startX */,
- height - 1 /* startY */,
- width / 2 /* endX */,
- 1 /* endY */,
- 50 /* numberOfSteps */);
- }
-
- private boolean isWatch() {
- return (getContext().getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH;
- }
-
- private Context getContext() {
- return InstrumentationRegistry.getInstrumentation().getContext();
- }
-
- private UiDevice getUiDevice() {
- return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- }
-
- private void waitForSyncManagerAccountChangeUpdate() {
- // Wait for the sync manager to be notified for the new account.
- // Unfortunately, there is no way to detect this event, sigh...
- SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
- }
-
- private boolean hasDataConnection() {
- ConnectivityManager connectivityManager = getContext().getSystemService(
- ConnectivityManager.class);
- NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
- return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
- }
-
- private boolean hasNotificationSupport() {
- final PackageManager manager = getContext().getPackageManager();
- return !manager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
- && !manager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
- }
-
- private void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
- // Allow us to run in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd deviceidle whitelist +" + getContext().getPackageName());
- // Allow us to use data in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
- }
-
- private void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
- // Allow us to run in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd deviceidle whitelist -" + getContext().getPackageName());
- // Allow us to use data in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
- }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk
deleted file mode 100644
index 0979e96..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- ctstestrunner \
- ub-uiautomator \
- compatibility-device-util
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSyncAccountAccessSameCertTestCases
-
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/AndroidManifest.xml b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/AndroidManifest.xml
deleted file mode 100644
index 2ecd27d..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.content">
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
- <application>
- <uses-library android:name="android.test.runner" />
-
- <activity android:name=".StubActivity"/>
-
- <service android:name=".SyncService">
- <intent-filter>
- <action android:name="android.content.SyncAdapter"/>
- </intent-filter>
- <meta-data android:name="android.content.SyncAdapter"
- android:resource="@xml/syncadapter" />
- </service>
-
- </application>
-
- <instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.content" />
-
-</manifest>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/res/xml/syncadapter.xml b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/res/xml/syncadapter.xml
deleted file mode 100644
index f55a19a..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/res/xml/syncadapter.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<sync-adapter
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:contentAuthority="com.android.cts.stub.provider"
- android:accountType="com.stub"
- android:userVisible="false"
- android:supportsUploading="false"
- android:allowParallelSyncs="false"
- android:isAlwaysSyncable="true">
-</sync-adapter>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/CtsSyncAccountAccessSameCertTestCases.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/CtsSyncAccountAccessSameCertTestCases.java
deleted file mode 100644
index bfdd072..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/CtsSyncAccountAccessSameCertTestCases.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import static junit.framework.Assert.assertTrue;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SyncRequest;
-import android.content.SyncResult;
-import android.content.cts.FlakyTestRule;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-/**
- * Tests whether a sync adapter can access accounts.
- */
-@RunWith(AndroidJUnit4.class)
-public class CtsSyncAccountAccessSameCertTestCases {
- private static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
-
- @Rule
- public final TestRule mFlakyTestTRule = new FlakyTestRule(3);
-
- @Before
- public void setUp() throws Exception {
- allowSyncAdapterRunInBackgroundAndDataInBackground();
- }
-
- @After
- public void tearDown() throws Exception {
- disallowSyncAdapterRunInBackgroundAndDataInBackground();
- }
-
- @Test
- public void testAccountAccess_sameCertAsAuthenticatorCanSeeAccount() throws Exception {
- if (!hasDataConnection() || !hasNotificationSupport()) {
- return;
- }
-
- Intent intent = new Intent(getContext(), StubActivity.class);
- Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-
- AccountManager accountManager = getContext().getSystemService(AccountManager.class);
- Bundle result = accountManager.addAccount("com.stub", null, null, null, activity,
- null, null).getResult();
-
- Account addedAccount = new Account(
- result.getString(AccountManager.KEY_ACCOUNT_NAME),
- result.getString(AccountManager.KEY_ACCOUNT_TYPE));
-
- waitForSyncManagerAccountChangeUpdate();
-
- try {
- CountDownLatch latch = new CountDownLatch(1);
-
- SyncAdapter.setOnPerformSyncDelegate((Account account, Bundle extras,
- String authority, ContentProviderClient provider, SyncResult syncResult)
- -> latch.countDown());
-
- Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
- SyncRequest request = new SyncRequest.Builder()
- .setSyncAdapter(null, "com.android.cts.stub.provider")
- .syncOnce()
- .setExtras(extras)
- .setExpedited(true)
- .setManual(true)
- .build();
- ContentResolver.requestSync(request);
-
- assertTrue(latch.await(SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
- } finally {
- accountManager.removeAccount(addedAccount, activity, null, null);
- activity.finish();
- }
- }
-
- private Context getContext() {
- return InstrumentationRegistry.getInstrumentation().getContext();
- }
-
- private void waitForSyncManagerAccountChangeUpdate() {
- // Wait for the sync manager to be notified for the new account.
- // Unfortunately, there is no way to detect this event, sigh...
- SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
- }
-
- private boolean hasDataConnection() {
- ConnectivityManager connectivityManager = getContext().getSystemService(
- ConnectivityManager.class);
- NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
- return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
- }
-
- private boolean hasNotificationSupport() {
- return !getContext().getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
- }
-
- private void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
- // Allow us to run in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd deviceidle whitelist +" + getContext().getPackageName());
- // Allow us to use data in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
- }
-
- private void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
- // Allow us to run in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd deviceidle whitelist -" + getContext().getPackageName());
- // Allow us to use data in the background
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
- }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java
deleted file mode 100644
index e5664f1..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/FlakyTestRule.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- */
-
-package android.content.cts;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Rule for running flaky tests that runs the test up to attempt
- * count and if one run succeeds reports the tests as passing.
- */
-// TODO: Move this puppy in a common place, so ppl can use it.
-public class FlakyTestRule implements TestRule {
- private final int mAttemptCount;
-
- public FlakyTestRule(int attemptCount) {
- mAttemptCount = attemptCount;
- }
-
- @Override
- public Statement apply(Statement statement, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- Throwable throwable = null;
- for (int i = 0; i < mAttemptCount; i++) {
- try {
- statement.evaluate();
- return;
- } catch (Throwable t) {
- throwable = t;
- }
- }
- throw throwable;
- };
- };
- }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java
deleted file mode 100644
index e93a070..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncAdapter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.content;
-
-import android.accounts.Account;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.content.SyncResult;
-import android.os.Bundle;
-
-public class SyncAdapter extends AbstractThreadedSyncAdapter {
- private static final Object sLock = new Object();
-
- private static OnPerformSyncDelegate sOnPerformSyncDelegate;
-
- public interface OnPerformSyncDelegate {
- void onPerformSync(Account account, Bundle extras, String authority,
- ContentProviderClient provider, SyncResult syncResult);
- }
-
- public static void setOnPerformSyncDelegate(OnPerformSyncDelegate delegate) {
- synchronized (sLock) {
- sOnPerformSyncDelegate = delegate;
- }
- }
-
- public SyncAdapter(Context context, boolean autoInitialize) {
- super(context, autoInitialize);
- }
-
- @Override
- public void onPerformSync(Account account, Bundle extras, String authority,
- ContentProviderClient provider, SyncResult syncResult) {
- OnPerformSyncDelegate delegate;
- synchronized (sLock) {
- delegate = sOnPerformSyncDelegate;
- }
- if (delegate != null) {
- delegate.onPerformSync(account, extras, authority, provider, syncResult);
- }
- }
-}
diff --git a/hostsidetests/cpptools/AndroidTest.xml b/hostsidetests/cpptools/AndroidTest.xml
index 94a1154..6e60080 100644
--- a/hostsidetests/cpptools/AndroidTest.xml
+++ b/hostsidetests/cpptools/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CPP host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="devtools" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/deviceidle/Android.mk b/hostsidetests/deviceidle/Android.mk
new file mode 100644
index 0000000..e73d70f
--- /dev/null
+++ b/hostsidetests/deviceidle/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsDeviceIdleHostTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+LOCAL_CTS_TEST_PACKAGE := android.deviceidle
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/deviceidle/AndroidTest.xml b/hostsidetests/deviceidle/AndroidTest.xml
new file mode 100644
index 0000000..fecade9
--- /dev/null
+++ b/hostsidetests/deviceidle/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for the CTS Content host tests">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsDeviceIdleHostTestCases.jar" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/hostsidetests/deviceidle/src/com/android/cts/deviceidle/DeviceIdleWhitelistTest.java b/hostsidetests/deviceidle/src/com/android/cts/deviceidle/DeviceIdleWhitelistTest.java
new file mode 100644
index 0000000..df16a81
--- /dev/null
+++ b/hostsidetests/deviceidle/src/com/android/cts/deviceidle/DeviceIdleWhitelistTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceidle;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import org.junit.Assume;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that it is possible to remove apps from the system whitelist
+ */
+public class DeviceIdleWhitelistTest extends DeviceTestCase implements IBuildReceiver {
+
+ private static final String DEVICE_IDLE_COMMAND_PREFIX = "cmd deviceidle sys-whitelist ";
+ private static final String RESET_SYS_WHITELIST_COMMAND = "cmd deviceidle sys-whitelist reset";
+ private static final String SHOW_SYS_WHITELIST_COMMAND = DEVICE_IDLE_COMMAND_PREFIX;
+
+ private List<String> mOriginalSystemWhitelist;
+ protected IBuildInfo mCtsBuild;
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getDevice().executeShellCommand(RESET_SYS_WHITELIST_COMMAND);
+ mOriginalSystemWhitelist = getSystemWhitelist();
+ if (mOriginalSystemWhitelist.size() < 1) {
+ LogUtil.CLog.w("No packages found in system whitelist");
+ Assume.assumeTrue(false);
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ getDevice().executeShellCommand(RESET_SYS_WHITELIST_COMMAND);
+ }
+
+ public void testRemoveFromSysWhitelist() throws Exception {
+ final String packageToRemove = mOriginalSystemWhitelist.get(0);
+ getDevice().executeShellCommand(DEVICE_IDLE_COMMAND_PREFIX + "-" + packageToRemove);
+ final List<String> newWhitelist = getSystemWhitelist();
+ assertFalse("Package " + packageToRemove + " not removed from whitelist",
+ newWhitelist.contains(packageToRemove));
+ }
+
+ public void testRemovesPersistedAcrossReboots() throws Exception {
+ for (int i = 0; i < mOriginalSystemWhitelist.size(); i+=2) {
+ // remove odd indexed packages from the whitelist
+ getDevice().executeShellCommand(
+ DEVICE_IDLE_COMMAND_PREFIX + "-" + mOriginalSystemWhitelist.get(i));
+ }
+ final List<String> whitelistBeforeReboot = getSystemWhitelist();
+ Thread.sleep(10_000); // write to disk happens after 5 seconds
+ getDevice().reboot();
+ Thread.sleep(5_000); // to make sure service is initialized
+ final List<String> whitelistAfterReboot = getSystemWhitelist();
+ assertEquals(whitelistBeforeReboot.size(), whitelistAfterReboot.size());
+ for (int i = 0; i < whitelistBeforeReboot.size(); i++) {
+ assertTrue(whitelistAfterReboot.contains(whitelistBeforeReboot.get(i)));
+ }
+ }
+
+ private List<String> getSystemWhitelist() throws DeviceNotAvailableException {
+ final String output = getDevice().executeShellCommand(SHOW_SYS_WHITELIST_COMMAND).trim();
+ final List<String> packages = new ArrayList<>();
+ for (String line : output.split("\n")) {
+ final int i = line.indexOf(',');
+ packages.add(line.substring(0, i));
+ }
+ return packages;
+ }
+}
diff --git a/hostsidetests/devicepolicy/Android.mk b/hostsidetests/devicepolicy/Android.mk
index 9cbd49e..95af5e7 100644
--- a/hostsidetests/devicepolicy/Android.mk
+++ b/hostsidetests/devicepolicy/Android.mk
@@ -27,7 +27,7 @@
LOCAL_CTS_TEST_PACKAGE := android.adminhostside
# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_COMPATIBILITY_SUITE := cts arcts vts general-tests
include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/devicepolicy/AndroidTest.xml b/hostsidetests/devicepolicy/AndroidTest.xml
index 51711dd..6fc38c8 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS Device Policy host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<!-- Push the list of public APIs to device -->
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
index 0873334..2943695 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.mk
@@ -31,10 +31,9 @@
android-support-v4 \
ctstestrunner \
ub-uiautomator \
- android-support-test \
- legacy-android-test
+ android-support-test
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml
index 1f488e5..97659b3 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/AndroidManifest.xml
@@ -27,6 +27,8 @@
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<service android:name="com.android.cts.devicepolicy.accountcheck.TestAuthenticator"
android:exported="true">
<intent-filter>
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/Android.mk b/hostsidetests/devicepolicy/app/AccountManagement/Android.mk
index b7d99bf..4b59d84 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/Android.mk
+++ b/hostsidetests/devicepolicy/app/AccountManagement/Android.mk
@@ -31,8 +31,9 @@
android-support-v4 \
ctstestrunner \
ub-uiautomator \
- android-support-test \
- legacy-android-test
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml
index b1e1b53..9c31a62 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountManagement/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<service android:name=".MockAccountService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
diff --git a/hostsidetests/devicepolicy/app/Assistant/Android.mk b/hostsidetests/devicepolicy/app/Assistant/Android.mk
index 86144a2..308c0a1 100644
--- a/hostsidetests/devicepolicy/app/Assistant/Android.mk
+++ b/hostsidetests/devicepolicy/app/Assistant/Android.mk
@@ -29,7 +29,11 @@
LOCAL_PACKAGE_NAME := CtsDevicePolicyAssistApp
-LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ compatibility-device-util \
+ android-support-test \
+
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml b/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml
index 5fc20de..17ca642 100644
--- a/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/Assistant/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="com.android.cts.devicepolicy.assistapp" >
<application>
+ <uses-library android:name="android.test.runner" />
+
<service android:name=".MyInteractionService"
android:label="CTS test voice interaction service"
@@ -45,4 +47,4 @@
android:targetPackage="com.android.cts.devicepolicy.assistapp"
android:label="Assistant related device policy CTS" />
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
index cf6b6e8..711f984 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
@@ -28,7 +28,7 @@
<service
android:name=".SimpleAutofillService"
- android:permission="android.permission.BIND_AUTOFILL" >
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk
index c8872b4..8af6492 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.mk
@@ -32,7 +32,7 @@
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
@@ -57,7 +57,7 @@
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl
index bc8d3d3..938ee07 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/src/com/android/cts/comp/ICrossUserService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
new file mode 100644
index 0000000..dd20c9a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsCrossProfileAppsTests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES = \
+ android-support-v4 \
+ ctstestrunner \
+ android-support-test \
+ truth-prebuilt \
+ ub-uiautomator
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/AndroidManifest.xml
new file mode 100644
index 0000000..79093d6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.crossprofileappstest">
+
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".MainActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".NonMainActivity" android:exported="true"/>
+
+ <activity android:name=".NonExportedActivity" android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.crossprofileappstest"
+ android:label="Launcher Apps CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/res/layout/main.xml b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/res/layout/main.xml
new file mode 100644
index 0000000..877d890
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/res/layout/main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/user_textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsNonTargetUserTest.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsNonTargetUserTest.java
new file mode 100644
index 0000000..79da49b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsNonTargetUserTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.crossprofileappstest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import android.content.Context;
+import android.content.pm.crossprofile.CrossProfileApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Test that runs {@link CrossProfileApps} APIs against non-valid target user.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrossProfileAppsNonTargetUserTest {
+ private static final String PARAM_TARGET_USER = "TARGET_USER";
+
+ private CrossProfileApps mCrossProfileApps;
+ private UserHandle mTargetUser;
+ private Context mContext;
+
+ @Before
+ public void setupCrossProfileApps() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+ }
+
+ @Before
+ public void readTargetUser() {
+ Context context = InstrumentationRegistry.getContext();
+ Bundle arguments = InstrumentationRegistry.getArguments();
+ UserManager userManager = context.getSystemService(UserManager.class);
+ final int userSn = Integer.parseInt(arguments.getString(PARAM_TARGET_USER));
+ mTargetUser = userManager.getUserForSerialNumber(userSn);
+ }
+
+ @Test
+ public void testTargetUserIsNotInGetTargetProfiles() {
+ List<UserHandle> targetProfiles = mCrossProfileApps.getTargetUserProfiles();
+ assertThat(targetProfiles).doesNotContain(mTargetUser);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testCannotStartActivity() {
+ mCrossProfileApps.startMainActivity(
+ MainActivity.getComponentName(mContext), mTargetUser);
+ }
+}
+
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsTargetUserTest.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsTargetUserTest.java
new file mode 100644
index 0000000..6b518b4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsTargetUserTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.crossprofileappstest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.pm.crossprofile.CrossProfileApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test that runs {@link CrossProfileApps} APIs against valid target user.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrossProfileAppsTargetUserTest {
+ private static final String PARAM_TARGET_USER = "TARGET_USER";
+ private static final String ID_USER_TEXTVIEW =
+ "com.android.cts.crossprofileappstest:id/user_textview";
+ private static final long TIMEOUT_WAIT_UI = TimeUnit.SECONDS.toMillis(10);
+
+ private CrossProfileApps mCrossProfileApps;
+ private UserHandle mTargetUser;
+ private Context mContext;
+ private UiDevice mDevice;
+ private long mUserSerialNumber;
+
+ @Before
+ public void setupCrossProfileApps() {
+ mContext = InstrumentationRegistry.getContext();
+ mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
+ }
+
+ @Before
+ public void wakeupDeviceAndPressHome() throws Exception {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mDevice.wakeUp();
+ mDevice.pressMenu();
+ mDevice.pressHome();
+ }
+
+ @Before
+ public void readTargetUser() {
+ Context context = InstrumentationRegistry.getContext();
+ Bundle arguments = InstrumentationRegistry.getArguments();
+ UserManager userManager = context.getSystemService(UserManager.class);
+ mUserSerialNumber = Long.parseLong(arguments.getString(PARAM_TARGET_USER));
+ mTargetUser = userManager.getUserForSerialNumber(mUserSerialNumber);
+ assertNotNull(mTargetUser);
+ }
+
+ @After
+ public void pressHome() {
+ mDevice.pressHome();
+ }
+
+ @Test
+ public void testTargetUserIsIngetTargetUserProfiles() {
+ List<UserHandle> targetProfiles = mCrossProfileApps.getTargetUserProfiles();
+ assertThat(targetProfiles).contains(mTargetUser);
+ }
+
+ /**
+ * Verify we succeed to start the activity in another profile by checking UI element.
+ */
+ @Test
+ public void testCanStartMainActivity() throws Exception {
+ mCrossProfileApps.startMainActivity(
+ MainActivity.getComponentName(mContext), mTargetUser);
+
+ // Look for the text view to verify that MainActivity is started.
+ UiObject2 textView = mDevice.wait(
+ Until.findObject(By.res(ID_USER_TEXTVIEW)),
+ TIMEOUT_WAIT_UI);
+ assertNotNull("Failed to start activity in target user", textView);
+ // Look for the text in textview, it should be the serial number of target user.
+ assertEquals("Activity is started in wrong user",
+ String.valueOf(mUserSerialNumber),
+ textView.getText());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testCannotStartNotExportedActivity() throws Exception {
+ mCrossProfileApps.startMainActivity(
+ NonExportedActivity.getComponentName(mContext), mTargetUser);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testCannotStartNonMainActivity() throws Exception {
+ mCrossProfileApps.startMainActivity(
+ NonExportedActivity.getComponentName(mContext), mTargetUser);
+ }
+}
+
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/MainActivity.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/MainActivity.java
new file mode 100644
index 0000000..c137e80
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/MainActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.lang.Override;
+
+/**
+ * An dummy activity that displays the serial number of the user that it is running into.
+ */
+public class MainActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.main);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ TextView textView = findViewById(R.id.user_textview);
+ textView.setText(Long.toString(getCurrentUserSerialNumber()));
+ }
+
+ public static ComponentName getComponentName(Context context) {
+ return new ComponentName(context, MainActivity.class);
+ }
+
+ private long getCurrentUserSerialNumber() {
+ UserManager userManager = getSystemService(UserManager.class);
+ return userManager.getSerialNumberForUser(Process.myUserHandle());
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
new file mode 100644
index 0000000..0876694
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonExportedActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+
+public class NonExportedActivity extends Activity {
+
+ public static ComponentName getComponentName(Context context ){
+ return new ComponentName(context, NonExportedActivity.class);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
new file mode 100644
index 0000000..56ec466
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+
+public class NonMainActivity extends Activity {
+
+ public static ComponentName getComponentName(Context context) {
+ return new ComponentName(context, NonMainActivity.class);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk b/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
index 1e6e2f1..2350e24 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/Android.mk
@@ -26,8 +26,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml
index 4b20829..be6249f 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/AndroidManifest.xml
@@ -23,6 +23,7 @@
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<application>
+ <uses-library android:name="android.test.runner" />
</application>
<instrumentation
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
index a46eed8..e7e986d 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/DelegateApp/Android.mk
@@ -24,13 +24,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES = \
android-support-v4 \
ctstestrunner \
- android-support-test \
- legacy-android-test
+ android-support-test
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
index a8f4f05..5cf7f05 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/Android.mk
@@ -28,10 +28,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- compatibility-device-util \
- legacy-android-test
+ compatibility-device-util
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
index 4e2cfb6..bdbbf89 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/Android.mk
@@ -28,10 +28,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- compatibility-device-util \
- legacy-android-test
+ compatibility-device-util
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk
index 72d2bb0..55cf2b3 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/Android.mk
@@ -28,6 +28,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
# tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk
index e2f9b8d..8725fb7 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/Android.mk
@@ -28,6 +28,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
# tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk
index b88d537..fdc7e7a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/Android.mk
@@ -28,6 +28,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
# tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk
index 7f72ddf..02bf1e5 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/Android.mk
@@ -28,6 +28,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
# tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk
index 8c66638..cf21905 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/Android.mk
@@ -28,6 +28,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
# tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
index c818856..71aeff6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
@@ -24,7 +24,11 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_JAVA_LIBRARIES = conscrypt legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+ conscrypt \
+ android.test.runner.stubs \
+ android.test.base.stubs \
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
index 1c50763..6e6227c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
@@ -24,7 +24,11 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_JAVA_LIBRARIES = conscrypt legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+ conscrypt \
+ android.test.runner.stubs \
+ android.test.base.stubs \
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
index 81a23d2..070c282 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
@@ -24,7 +24,11 @@
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_JAVA_LIBRARIES = conscrypt
+LOCAL_JAVA_LIBRARIES := \
+ conscrypt \
+ android.test.runner.stubs \
+ android.test.base.stubs \
+
LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 compatibility-device-util ctstestrunner ub-uiautomator
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java
index efc7115..f26d228 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AudioRestrictionTest.java
@@ -83,8 +83,8 @@
}
try {
- // Set volume of ringtone to be 1.
- mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, /* flag= */ 0);
+ // Set volume of music to be 1.
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, /* flag= */ 0);
// Disallow adjusting volume.
mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
@@ -93,7 +93,7 @@
// Verify that volume can't be changed.
mAudioManager.adjustVolume(AudioManager.ADJUST_RAISE, /* flag= */ 0);
- assertEquals(1, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+ assertEquals(1, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
// Allowing adjusting volume.
mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
@@ -105,7 +105,7 @@
waitUntil(2, new Callable<Integer>() {
@Override
public Integer call() throws Exception {
- return mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
+ return mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
}
});
} finally {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java
new file mode 100644
index 0000000..4c1a974
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.os.AsyncTask;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class that calls DPM.clearApplicationUserData and verifies that it doesn't time out.
+ */
+public class ClearApplicationDataTest extends BaseDeviceAdminTest {
+ private static final String TEST_PKG = "com.android.cts.intent.receiver";
+ private static final Semaphore mSemaphore = new Semaphore(0);
+ private static final long CLEAR_APPLICATION_DATA_TIMEOUT_S = 10;
+
+ public void testClearApplicationData() throws Exception {
+ mDevicePolicyManager.clearApplicationUserData(ADMIN_RECEIVER_COMPONENT, TEST_PKG,
+ (String pkg, boolean succeeded) -> {
+ assertEquals(TEST_PKG, pkg);
+ assertTrue(succeeded);
+ mSemaphore.release();
+ }, AsyncTask.THREAD_POOL_EXECUTOR);
+
+ assertTrue("Clearing application data took too long",
+ mSemaphore.tryAcquire(CLEAR_APPLICATION_DATA_TIMEOUT_S, TimeUnit.SECONDS));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordBlacklistTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordBlacklistTest.java
new file mode 100644
index 0000000..7856aff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordBlacklistTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class PasswordBlacklistTest extends BaseDeviceAdminTest {
+ private static final String TAG = "PasswordBlacklistTest";
+ private static final byte[] TOKEN = "abcdefghijklmnopqrstuvwxyz0123456789".getBytes();
+
+ private boolean mShouldRun = true;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Set up a token to reset the password. This is used to check the blacklist is being
+ // enforced.
+ try {
+ // On devices with password token disabled, calling this method will throw
+ // a security exception. If that's anticipated, then return early without failing.
+ assertTrue(mDevicePolicyManager.setResetPasswordToken(ADMIN_RECEIVER_COMPONENT,
+ TOKEN));
+ } catch (SecurityException e) {
+ if (e.getMessage().equals("Escrow token is disabled on the current user")) {
+ Log.i(TAG, "Skip some password blacklist test because escrow token is disabled");
+ mShouldRun = false;
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (!mShouldRun) {
+ return;
+ }
+ // Remove the blacklist, password and password reset token
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(ADMIN_RECEIVER_COMPONENT, null, null));
+ assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, null, TOKEN, 0));
+ assertTrue(mDevicePolicyManager.clearResetPasswordToken(ADMIN_RECEIVER_COMPONENT));
+ }
+
+ public void testSettingEmptyBlacklist() {
+ if (!mShouldRun) {
+ return;
+ }
+ final String notInBlacklist = "4ur3>#C$a#rC3W9Rhs";
+
+ testPasswordBlacklist(null, notInBlacklist);
+ }
+
+ public void testClearingBlacklist() {
+ if (!mShouldRun) {
+ return;
+ }
+ final String notInBlacklist = "4ur3>#C$a#rC3W9Rhs";
+
+ testPasswordBlacklist(Arrays.asList(notInBlacklist), null);
+ testPasswordBlacklist(null, notInBlacklist);
+ }
+
+ public void testSettingBlacklist() {
+ if (!mShouldRun) {
+ return;
+ }
+ final List<String> blacklist = Arrays.asList("password", "letmein", "football");
+ final String notInBlacklist = "falseponycellfastener";
+
+ testPasswordBlacklist(blacklist, notInBlacklist);
+ }
+
+ public void testChangingBlacklist() {
+ if (!mShouldRun) {
+ return;
+ }
+ final List<String> blacklist = Arrays.asList("password", "letmein", "football");
+ final String notInBlacklist = "falseponycellfastener";
+
+ testPasswordBlacklist(Arrays.asList(notInBlacklist), null);
+ testPasswordBlacklist(blacklist, notInBlacklist);
+ }
+
+ public void testBlacklistNotTreatedAsRegex() {
+ if (!mShouldRun) {
+ return;
+ }
+ final List<String> blacklist = Arrays.asList("hi\\d*", ".*", "[^adb]{2}.\\d.\\S");
+ final String notInBlacklist = "hi123";
+
+ testPasswordBlacklist(blacklist, notInBlacklist);
+ }
+
+ public void testBlacklistCaseInsensitive() {
+ if (!mShouldRun) {
+ return;
+ }
+ final List<String> blacklist = Arrays.asList("baseball", "MONKEY", "ShAdOw");
+ final String notInBlacklist = "falsecellfastenerpony";
+
+ testPasswordBlacklist(blacklist, notInBlacklist);
+
+ // These are also blocked by the blacklist as they only differ in case
+ final List<String> inBlacklist = Arrays.asList(
+ "baseball", "BASEBALL", "BASEball",
+ "monkey", "MONKEY", "moNKEy",
+ "shadow", "SHADOW", "ShAdOw");
+ for (final String password : inBlacklist) {
+ assertFalse(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+ }
+ }
+
+ public void testMaxBlacklistSize() {
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, "max size", generateMaxBlacklist()));
+ }
+
+ public void testBlacklistTooBig() {
+ try {
+ mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, "too big", generateJustTooBigBlacklist());
+ fail("Did not throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ return;
+ }
+ }
+
+ public void testNullNameWhenSettingBlacklist() {
+ if (!mShouldRun) {
+ return;
+ }
+ final String password = "bad one";
+ try {
+ mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, null, Arrays.asList(password));
+ fail("Did not throw NullPointerException");
+ } catch (NullPointerException e) {
+ assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+ return;
+ }
+ }
+
+ public void testNullAdminWhenSettingBlacklist() {
+ if (!mShouldRun) {
+ return;
+ }
+ final String password = "example";
+ try {
+ mDevicePolicyManager.setPasswordBlacklist(null, "no admin", Arrays.asList(password));
+ fail("Did not throw NullPointerException");
+ } catch (NullPointerException e) {
+ assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+ return;
+ }
+ }
+
+ public void testPasswordBlacklistName() {
+ if (!mShouldRun) {
+ return;
+ }
+ final String name = "Version 1.0";
+ final List<String> blacklist = Arrays.asList("one", "1", "i");
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, name, blacklist));
+ assertEquals(
+ mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), name);
+ for (final String password : blacklist) {
+ assertFalse(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+ }
+ assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, "notintheblacklist", TOKEN, 0));
+ }
+
+ public void testPasswordBlacklistWithEmptyName() {
+ final String emptyName = "";
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, emptyName, Arrays.asList("test", "empty", "name")));
+ assertEquals(
+ mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), emptyName);
+ }
+
+ public void testBlacklistNameCanBeChanged() {
+ final String firstName = "original";
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, firstName, Arrays.asList("a")));
+ assertEquals(
+ mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), firstName);
+
+ final String newName = "different";
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, newName, Arrays.asList("a")));
+ assertEquals(
+ mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), newName);
+ }
+
+ public void testCannotNameClearedBlacklist() {
+ final String name = "empty!";
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, name, null));
+ assertTrue(mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT) == null);
+ }
+
+ public void testClearingBlacklistClearsName() {
+ final String firstName = "gotone";
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, firstName, Arrays.asList("something")));
+ assertEquals(
+ mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT), firstName);
+
+ final String newName = "empty!";
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, newName, null));
+ assertTrue(mDevicePolicyManager.getPasswordBlacklistName(ADMIN_RECEIVER_COMPONENT) == null);
+ }
+
+ public void testNullAdminWhenGettingBlacklistName() {
+ try {
+ mDevicePolicyManager.getPasswordBlacklistName(null);
+ fail("Did not throw NullPointerException");
+ } catch (NullPointerException e) {
+ return;
+ }
+ }
+
+ public void testBlacklistNotConsideredByIsActivePasswordSufficient() {
+ if (!mShouldRun) {
+ return;
+ }
+ mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+ final String complexPassword = ".password123";
+ assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, complexPassword, TOKEN, 0));
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, "Sufficient", Arrays.asList(complexPassword)));
+ assertPasswordSufficiency(true);
+ }
+
+ private static final int MAX_BLACKLIST_ITEM_SIZE = 8;
+
+ /* Generate a list based on the 128 thousand character limit */
+ private List<String> generateMaxBlacklist() {
+ final int numItems = (128 * 1000) / MAX_BLACKLIST_ITEM_SIZE;
+ assertTrue(numItems == 16 * 1000);
+ final List<String> blacklist = new ArrayList(numItems);
+ final String item = new String(new char[MAX_BLACKLIST_ITEM_SIZE]).replace('\0', 'a');
+ for (int i = 0; i < numItems; ++i) {
+ blacklist.add(item);
+ }
+ return blacklist;
+ }
+
+ private List<String> generateJustTooBigBlacklist() {
+ List<String> list = generateMaxBlacklist();
+ list.set(0, new String(new char[MAX_BLACKLIST_ITEM_SIZE + 1]).replace('\0', 'a'));
+ return list;
+ }
+
+ /**
+ * Install a blacklist, ensure items match and don't match it correctly.
+ */
+ private void testPasswordBlacklist(List<String> blacklist, String notInBlacklist) {
+ assertTrue(mDevicePolicyManager.setPasswordBlacklist(
+ ADMIN_RECEIVER_COMPONENT, "Test Blacklist", blacklist));
+
+ if (blacklist != null) {
+ // These are blacklisted so can't be set
+ for (final String password : blacklist) {
+ assertFalse(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, password, TOKEN, 0));
+ }
+ }
+
+ if (notInBlacklist != null) {
+ // This isn't blacklisted so can be set
+ assertTrue(mDevicePolicyManager.resetPasswordWithToken(
+ ADMIN_RECEIVER_COMPONENT, notInBlacklist, TOKEN, 0));
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 6c706ee..70a5d09 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.os.UserManager;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
@@ -47,6 +46,7 @@
private static final String SIMPLE_PRE_M_APP_PACKAGE_NAME =
"com.android.cts.launcherapps.simplepremapp";
private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
+ private static final String DEVELOPMENT_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
private static final String PERMISSIONS_ACTIVITY_NAME
= PERMISSION_APP_PACKAGE_NAME + ".PermissionActivity";
@@ -227,6 +227,15 @@
assertSetPermissionGrantStatePreMApp(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
}
+ public void testPermissionGrantState_developmentPermission() throws Exception {
+ assertFailedToSetDevelopmentPermissionGrantState(
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
+ assertFailedToSetDevelopmentPermissionGrantState(
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ assertFailedToSetDevelopmentPermissionGrantState(
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+ }
+
private void assertPermissionRequest(int expected) throws Exception {
assertPermissionRequest(expected, null);
}
@@ -274,6 +283,18 @@
value);
}
+ private void assertFailedToSetDevelopmentPermissionGrantState(int value) throws Exception {
+ assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION, value));
+ assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+ PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION),
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+ assertEquals(mPackageManager.checkPermission(DEVELOPMENT_PERMISSION,
+ PERMISSION_APP_PACKAGE_NAME),
+ PackageManager.PERMISSION_DENIED);
+ }
+
+
private void assertSetPermissionGrantStatePreMApp(int value) throws Exception {
assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index 39235b0..0361fd8 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -60,6 +60,7 @@
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_FUN,
UserManager.DISALLOW_CREATE_WINDOWS,
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
UserManager.DISALLOW_OUTGOING_BEAM,
UserManager.DISALLOW_SAFE_BOOT,
@@ -91,7 +92,8 @@
// PO can set them too, but when DO sets them, they're global.
UserManager.DISALLOW_ADJUST_VOLUME,
- UserManager.DISALLOW_UNMUTE_MICROPHONE
+ UserManager.DISALLOW_UNMUTE_MICROPHONE,
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS
};
public static final String[] HIDDEN_AND_PROHIBITED = new String[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
index b218341..9743840 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
@@ -48,6 +48,7 @@
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_FUN,
UserManager.DISALLOW_CREATE_WINDOWS,
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
UserManager.DISALLOW_OUTGOING_BEAM,
UserManager.DISALLOW_SAFE_BOOT,
@@ -55,7 +56,8 @@
// UserManager.DISALLOW_DATA_ROAMING, // Has unrecoverable side effects.
UserManager.DISALLOW_SET_USER_ICON,
UserManager.DISALLOW_BLUETOOTH,
- UserManager.DISALLOW_AUTOFILL
+ UserManager.DISALLOW_AUTOFILL,
+ UserManager.DISALLOW_UNIFIED_PASSWORD,
};
public static final String[] DISALLOWED = new String[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java
index 5c7f243..2a9db15 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/SecondaryProfileOwnerUserRestrictionsTest.java
@@ -38,11 +38,13 @@
UserManager.DISALLOW_UNMUTE_MICROPHONE,
UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_OUTGOING_CALLS,
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
UserManager.DISALLOW_OUTGOING_BEAM,
UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
UserManager.DISALLOW_SET_USER_ICON,
- UserManager.DISALLOW_AUTOFILL
+ UserManager.DISALLOW_AUTOFILL,
+ UserManager.DISALLOW_UNIFIED_PASSWORD,
};
public static final String[] DISALLOWED = new String[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index 4cc041a..e0ea522 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -22,16 +22,25 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
+
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ conscrypt \
+ cts-junit \
+ android.test.base.stubs \
+ bouncycastle
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
compatibility-device-util \
android-support-v4 \
android-support-test \
- legacy-android-test
+ bouncycastle \
+ cts-security-test-support-library
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 9a09007..bdb5d3b 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -35,7 +35,7 @@
<uses-library android:name="android.test.runner" />
<receiver
- android:name="com.android.cts.deviceowner.BaseDeviceOwnerTest$BasicAdminReceiver"
+ android:name="com.android.cts.deviceowner.BasicAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data android:name="android.app.device_admin"
android:resource="@xml/device_admin" />
@@ -52,6 +52,20 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
+ <receiver
+ android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin" />
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+ </receiver>
+
+ <service android:name="com.android.cts.deviceowner.CreateAndManageUserTest$PrimaryUserService"
+ android:exported="true"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ </service>
<activity
android:name="com.android.cts.deviceowner.KeyManagementActivity"
@@ -70,14 +84,6 @@
</intent-filter>
</activity>
- <!-- we need to give a different taskAffinity so that when we use
- FLAG_ACTIVITY_NEW_TASK, the system tries to start it in a different task
- -->
- <activity
- android:name="com.android.cts.deviceowner.LockTaskTest$IntentReceivingActivity"
- android:taskAffinity="com.android.cts.deviceowner.LockTaskTest.IntentReceivingActivity"
- />
-
<activity
android:name=".SetPolicyActivity"
android:launchMode="singleTop">
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
index a9b43c6..0afc16e 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
@@ -46,7 +46,7 @@
Context context = InstrumentationRegistry.getContext();
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
- mAdminComponent = BaseDeviceOwnerTest.BasicAdminReceiver.getComponentName(context);
+ mAdminComponent = BasicAdminReceiver.getComponentName(context);
}
@Test
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseAffiliatedProfileOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseAffiliatedProfileOwnerTest.java
new file mode 100644
index 0000000..9490eff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseAffiliatedProfileOwnerTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.test.AndroidTestCase;
+
+/**
+ * Base class for affiliated profile-owner based tests.
+ *
+ * This class handles making sure that the test is the affiliated profile owner and that it has an
+ * active admin registered, so that all tests may assume these are done. The admin component can be
+ * accessed through {@link #getWho()}.
+ */
+public abstract class BaseAffiliatedProfileOwnerTest extends AndroidTestCase {
+
+ protected DevicePolicyManager mDevicePolicyManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ assertDeviceOrAffiliatedProfileOwner();
+ }
+
+ private void assertDeviceOrAffiliatedProfileOwner() {
+ assertNotNull(mDevicePolicyManager);
+ assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
+ boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName());
+ boolean isAffiliatedProfileOwner = mDevicePolicyManager.isProfileOwnerApp(
+ mContext.getPackageName())
+ && mDevicePolicyManager.isAffiliatedUser();
+ assertTrue(isDeviceOwner || isAffiliatedProfileOwner);
+ }
+
+ protected ComponentName getWho() {
+ return BasicAdminReceiver.getComponentName(mContext);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 94aaeb2..6ed1ca7 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -15,15 +15,8 @@
*/
package com.android.cts.deviceowner;
-import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Process;
-import android.os.UserHandle;
-import android.support.v4.content.LocalBroadcastManager;
import android.test.AndroidTestCase;
/**
@@ -36,77 +29,24 @@
*/
public abstract class BaseDeviceOwnerTest extends AndroidTestCase {
- final static String ACTION_USER_ADDED = "com.android.cts.deviceowner.action.USER_ADDED";
- final static String ACTION_USER_REMOVED = "com.android.cts.deviceowner.action.USER_REMOVED";
- final static String EXTRA_USER_HANDLE = "com.android.cts.deviceowner.extra.USER_HANDLE";
- final static String ACTION_NETWORK_LOGS_AVAILABLE =
- "com.android.cts.deviceowner.action.ACTION_NETWORK_LOGS_AVAILABLE";
- final static String EXTRA_NETWORK_LOGS_BATCH_TOKEN =
- "com.android.cts.deviceowner.extra.NETWORK_LOGS_BATCH_TOKEN";
-
- public static class BasicAdminReceiver extends DeviceAdminReceiver {
-
- public static ComponentName getComponentName(Context context) {
- return new ComponentName(context, BasicAdminReceiver.class);
- }
-
- @Override
- public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
- String suggestedAlias) {
- if (uid != Process.myUid() || uri == null) {
- return null;
- }
- return uri.getQueryParameter("alias");
- }
-
- @Override
- public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
- sendUserAddedOrRemovedBroadcast(context, ACTION_USER_ADDED, userHandle);
- }
-
- @Override
- public void onUserRemoved(Context context, Intent intent, UserHandle userHandle) {
- sendUserAddedOrRemovedBroadcast(context, ACTION_USER_REMOVED, userHandle);
- }
-
- @Override
- public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
- int networkLogsCount) {
- // send the broadcast, the rest of the test happens in NetworkLoggingTest
- Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
- batchIntent.putExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, batchToken);
- LocalBroadcastManager.getInstance(context).sendBroadcast(batchIntent);
- }
-
- private void sendUserAddedOrRemovedBroadcast(Context context, String action,
- UserHandle userHandle) {
- Intent intent = new Intent(action);
- intent.putExtra(EXTRA_USER_HANDLE, userHandle);
- LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
- }
- }
-
- public static final String PACKAGE_NAME = BaseDeviceOwnerTest.class.getPackage().getName();
-
protected DevicePolicyManager mDevicePolicyManager;
@Override
protected void setUp() throws Exception {
super.setUp();
- mDevicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- assertDeviceOwner(mDevicePolicyManager);
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ assertDeviceOwner();
}
- static void assertDeviceOwner(DevicePolicyManager dpm) {
- assertNotNull(dpm);
- assertTrue(dpm.isAdminActive(getWho()));
- assertTrue(dpm.isDeviceOwnerApp(PACKAGE_NAME));
- assertFalse(dpm.isManagedProfile(getWho()));
+ private void assertDeviceOwner() {
+ assertNotNull(mDevicePolicyManager);
+ assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
+ assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName()));
+ assertFalse(mDevicePolicyManager.isManagedProfile(getWho()));
}
- protected static ComponentName getWho() {
- return new ComponentName(PACKAGE_NAME, BasicAdminReceiver.class.getName());
+ protected ComponentName getWho() {
+ return BasicAdminReceiver.getComponentName(mContext);
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
new file mode 100644
index 0000000..c4d710b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class BasicAdminReceiver extends DeviceAdminReceiver {
+
+ final static String ACTION_USER_ADDED = "com.android.cts.deviceowner.action.USER_ADDED";
+ final static String ACTION_USER_REMOVED = "com.android.cts.deviceowner.action.USER_REMOVED";
+ final static String EXTRA_USER_HANDLE = "com.android.cts.deviceowner.extra.USER_HANDLE";
+ final static String ACTION_NETWORK_LOGS_AVAILABLE =
+ "com.android.cts.deviceowner.action.ACTION_NETWORK_LOGS_AVAILABLE";
+ final static String EXTRA_NETWORK_LOGS_BATCH_TOKEN =
+ "com.android.cts.deviceowner.extra.NETWORK_LOGS_BATCH_TOKEN";
+
+ public static ComponentName getComponentName(Context context) {
+ return new ComponentName(context, BasicAdminReceiver.class);
+ }
+
+ @Override
+ public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
+ String suggestedAlias) {
+ if (uid != Process.myUid() || uri == null) {
+ return null;
+ }
+ return uri.getQueryParameter("alias");
+ }
+
+ @Override
+ public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
+ sendUserAddedOrRemovedBroadcast(context, ACTION_USER_ADDED, userHandle);
+ }
+
+ @Override
+ public void onUserRemoved(Context context, Intent intent, UserHandle userHandle) {
+ sendUserAddedOrRemovedBroadcast(context, ACTION_USER_REMOVED,
+ userHandle);
+ }
+
+ @Override
+ public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+ int networkLogsCount) {
+ // send the broadcast, the rest of the test happens in NetworkLoggingTest
+ Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
+ batchIntent.putExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, batchToken);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(batchIntent);
+ }
+
+ private void sendUserAddedOrRemovedBroadcast(Context context, String action,
+ UserHandle userHandle) {
+ Intent intent = new Intent(action);
+ intent.putExtra(EXTRA_USER_HANDLE, userHandle);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index a540c27..aa71bc2 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
@@ -17,6 +17,7 @@
package com.android.cts.deviceowner;
import android.app.ActivityManager;
+import android.app.Service;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -24,16 +25,21 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.os.Bundle;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
-import java.lang.reflect.Field;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
@@ -50,6 +56,12 @@
private static final String SETUP_COMPLETE_EXTRA = "setupCompleteExtra";
private static final int BROADCAST_TIMEOUT = 15_000;
private static final int USER_SWITCH_DELAY = 10_000;
+
+ private static final String AFFILIATION_ID = "affiliation.id";
+ private static final String EXTRA_AFFILIATION_ID = "affiliationIdExtra";
+ private static final long ON_ENABLED_TIMEOUT_SECONDS = 120;
+
+
private PackageManager mPackageManager;
private ActivityManager mActivityManager;
private volatile boolean mReceived;
@@ -122,75 +134,74 @@
}
}
-// Disabled due to b/29072728
-// // This test will create a user that will get passed a bundle that we specify. The bundle will
-// // contain an action and a serial (for user handle) to broadcast to notify the test that the
-// // configuration was triggered.
-// private void createAndManageUserTest(final int flags) {
-// // This test sets a profile owner on the user, which requires the managed_users feature.
-// if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-// return;
-// }
-//
-// final boolean expectedSetupComplete = (flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0;
-// UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-//
-// UserHandle firstUser = Process.myUserHandle();
-// final String testUserName = "TestUser_" + System.currentTimeMillis();
-// String action = "com.android.cts.TEST_USER_ACTION";
-// PersistableBundle bundle = new PersistableBundle();
-// bundle.putBoolean(BROADCAST_EXTRA, true);
-// bundle.putLong(SERIAL_EXTRA, userManager.getSerialNumberForUser(firstUser));
-// bundle.putString(ACTION_EXTRA, action);
-//
-// mReceived = false;
-// mTestProfileOwnerWasUsed = false;
-// mSetupComplete = !expectedSetupComplete;
-// BroadcastReceiver receiver = new BroadcastReceiver() {
-// @Override
-// public void onReceive(Context context, Intent intent) {
-// mReceived = true;
-// if (intent.getBooleanExtra(PROFILE_OWNER_EXTRA, false)) {
-// mTestProfileOwnerWasUsed = true;
-// }
-// mSetupComplete = intent.getBooleanExtra(SETUP_COMPLETE_EXTRA,
-// !expectedSetupComplete);
-// synchronized (CreateAndManageUserTest.this) {
-// CreateAndManageUserTest.this.notify();
-// }
-// }
-// };
-//
-// IntentFilter filter = new IntentFilter();
-// filter.addAction(action);
-// mContext.registerReceiver(receiver, filter);
-//
-// synchronized (this) {
-// mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), testUserName,
-// TestProfileOwner.getComponentName(), bundle, flags);
-// assertNotNull(mUserHandle);
-//
-// mDevicePolicyManager.switchUser(getWho(), mUserHandle);
-// try {
-// wait(USER_SWITCH_DELAY);
-// } catch (InterruptedException e) {
-// fail("InterruptedException: " + e.getMessage());
-// }
-// mDevicePolicyManager.switchUser(getWho(), firstUser);
-//
-// waitForBroadcastLocked();
-//
-// assertTrue(mReceived);
-// assertTrue(mTestProfileOwnerWasUsed);
-// assertEquals(expectedSetupComplete, mSetupComplete);
-//
-// assertTrue(mDevicePolicyManager.removeUser(getWho(), mUserHandle));
-//
-// mUserHandle = null;
-// }
-//
-// mContext.unregisterReceiver(receiver);
-// }
+ // This test will create a user that will get passed a bundle that we specify. The bundle will
+ // contain an action and a serial (for user handle) to broadcast to notify the test that the
+ // configuration was triggered.
+ private void createAndManageUserTest(final int flags) {
+ // This test sets a profile owner on the user, which requires the managed_users feature.
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
+ return;
+ }
+
+ final boolean expectedSetupComplete = (flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0;
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ UserHandle firstUser = Process.myUserHandle();
+ final String testUserName = "TestUser_" + System.currentTimeMillis();
+ String action = "com.android.cts.TEST_USER_ACTION";
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(BROADCAST_EXTRA, true);
+ bundle.putLong(SERIAL_EXTRA, userManager.getSerialNumberForUser(firstUser));
+ bundle.putString(ACTION_EXTRA, action);
+
+ mReceived = false;
+ mTestProfileOwnerWasUsed = false;
+ mSetupComplete = !expectedSetupComplete;
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mReceived = true;
+ if (intent.getBooleanExtra(PROFILE_OWNER_EXTRA, false)) {
+ mTestProfileOwnerWasUsed = true;
+ }
+ mSetupComplete = intent.getBooleanExtra(SETUP_COMPLETE_EXTRA,
+ !expectedSetupComplete);
+ synchronized (CreateAndManageUserTest.this) {
+ CreateAndManageUserTest.this.notify();
+ }
+ }
+ };
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(action);
+ mContext.registerReceiver(receiver, filter);
+
+ synchronized (this) {
+ mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), testUserName,
+ TestProfileOwner.getComponentName(), bundle, flags);
+ assertNotNull(mUserHandle);
+
+ mDevicePolicyManager.switchUser(getWho(), mUserHandle);
+ try {
+ wait(USER_SWITCH_DELAY);
+ } catch (InterruptedException e) {
+ fail("InterruptedException: " + e.getMessage());
+ }
+ mDevicePolicyManager.switchUser(getWho(), firstUser);
+
+ waitForBroadcastLocked();
+
+ assertTrue(mReceived);
+ assertTrue(mTestProfileOwnerWasUsed);
+ assertEquals(expectedSetupComplete, mSetupComplete);
+
+ assertTrue(mDevicePolicyManager.removeUser(getWho(), mUserHandle));
+
+ mUserHandle = null;
+ }
+
+ mContext.unregisterReceiver(receiver);
+ }
/**
* Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
@@ -204,59 +215,27 @@
public void testCreateAndManageEphemeralUser() throws Exception {
String testUserName = "TestUser_" + System.currentTimeMillis();
- // Use reflection to get the value of the hidden flag to make the new user ephemeral.
- Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
- int makeEphemeralFlag = field.getInt(null);
-
// Do not assign return value to mUserHandle, so it is not removed in tearDown.
mDevicePolicyManager.createAndManageUser(
getWho(),
testUserName,
getWho(),
null,
- makeEphemeralFlag);
+ DevicePolicyManager.MAKE_USER_EPHEMERAL);
}
- /**
- * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
- * method fails on systems without the split system user.
- *
- * <p>To be used by host-side test on systems without the split system user.
- */
- public void testCreateAndManageEphemeralUserFails() throws Exception {
- String testUserName = "TestUser_" + System.currentTimeMillis();
+ public void testCreateAndManageUser_SkipSetupWizard() {
+ createAndManageUserTest(DevicePolicyManager.SKIP_SETUP_WIZARD);
+ }
- // Use reflection to get the value of the hidden flag to make the new user ephemeral.
- Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
- int makeEphemeralFlag = field.getInt(null);
-
- try {
- mDevicePolicyManager.createAndManageUser(
- getWho(),
- testUserName,
- getWho(),
- null,
- makeEphemeralFlag);
- } catch (IllegalArgumentException e) {
- // Success, the expected exception was thrown.
- return;
+ public void testCreateAndManageUser_DontSkipSetupWizard() {
+ if (!mActivityManager.isRunningInTestHarness()) {
+ // In test harness, the setup wizard will be disabled by default, so this test is always
+ // failing.
+ createAndManageUserTest(0);
}
- fail("createAndManageUser should have thrown IllegalArgumentException");
}
-// Disabled due to b/29072728
-// public void testCreateAndManageUser_SkipSetupWizard() {
-// createAndManageUserTest(DevicePolicyManager.SKIP_SETUP_WIZARD);
-// }
-//
-// public void testCreateAndManageUser_DontSkipSetupWizard() {
-// if (!mActivityManager.isRunningInTestHarness()) {
-// // In test harness, the setup wizard will be disabled by default, so this test is always
-// // failing.
-// createAndManageUserTest(0);
-// }
-// }
-
// createAndManageUser should circumvent the DISALLOW_ADD_USER restriction
public void testCreateAndManageUser_AddRestrictionSet() {
mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
@@ -283,7 +262,8 @@
LocalBroadcastReceiver receiver = new LocalBroadcastReceiver();
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
getContext());
- localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_USER_ADDED));
+ localBroadcastManager.registerReceiver(receiver,
+ new IntentFilter(BasicAdminReceiver.ACTION_USER_ADDED));
try {
mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
null, 0);
@@ -292,7 +272,8 @@
} finally {
localBroadcastManager.unregisterReceiver(receiver);
}
- localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_USER_REMOVED));
+ localBroadcastManager.registerReceiver(receiver,
+ new IntentFilter(BasicAdminReceiver.ACTION_USER_REMOVED));
try {
assertTrue(mDevicePolicyManager.removeUser(getWho(), mUserHandle));
assertEquals(mUserHandle, receiver.waitForBroadcastReceived());
@@ -302,12 +283,34 @@
}
}
+ public void testCreateAndManageUser_StartUserInBackground() throws Exception {
+ String testUserName = "TestUser_" + System.currentTimeMillis();
+
+ // Set affiliation id to allow communication
+ mDevicePolicyManager.setAffiliationIds(getWho(), Collections.singleton(AFFILIATION_ID));
+
+ // Pack the affiliation id in a bundle so the secondary user can get it
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putString(EXTRA_AFFILIATION_ID, AFFILIATION_ID);
+
+ // Do not assign return value to mUserHandle, so it is not removed in tearDown.
+ UserHandle uh = mDevicePolicyManager.createAndManageUser(
+ getWho(),
+ testUserName,
+ SecondaryUserAdminReceiver.getComponentName(mContext),
+ bundle,
+ DevicePolicyManager.START_USER_IN_BACKGROUND);
+ Log.d(TAG, "User create: " + uh);
+
+ PrimaryUserService.assertCrossUserCallArrived();
+ }
+
static class LocalBroadcastReceiver extends BroadcastReceiver {
private SynchronousQueue<UserHandle> mQueue = new SynchronousQueue<UserHandle>();
@Override
public void onReceive(Context context, Intent intent) {
- UserHandle userHandle = intent.getParcelableExtra(EXTRA_USER_HANDLE);
+ UserHandle userHandle = intent.getParcelableExtra(BasicAdminReceiver.EXTRA_USER_HANDLE);
Log.d(TAG, "broadcast receiver received " + intent + " with userHandle "
+ userHandle);
mQueue.offer(userHandle);
@@ -318,4 +321,75 @@
return mQueue.poll(BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
}
}
+
+ public static final class PrimaryUserService extends Service {
+ private static final Semaphore mSemaphore = new Semaphore(0);
+
+ private final ICrossUserService.Stub mBinder = new ICrossUserService.Stub() {
+ public void onEnabledCalled() {
+ Log.d(TAG, "onEnabledCalled on primary user");
+ mSemaphore.release();
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ static void assertCrossUserCallArrived() throws Exception {
+ assertTrue(mSemaphore.tryAcquire(ON_ENABLED_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+ }
+ }
+
+ public static final class SecondaryUserAdminReceiver extends DeviceAdminReceiver {
+ @Override
+ public void onEnabled(Context context, Intent intent) {
+ Log.d(TAG, "onEnabled called");
+ DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ // Set affiliation ids
+ String affiliationId = intent.getStringExtra(EXTRA_AFFILIATION_ID);
+ dpm.setAffiliationIds(getComponentName(context),
+ Collections.singleton(affiliationId));
+ // Call all affiliated users
+ final List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(
+ getComponentName(context));
+ assertEquals(1, targetUsers.size());
+ pingTargetUser(context, dpm, targetUsers.get(0));
+ }
+
+ private void pingTargetUser(Context context, DevicePolicyManager dpm, UserHandle target) {
+ Log.d(TAG, "Pinging target: " + target);
+ final ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.d(TAG, "onServiceConnected is called in " + Thread.currentThread().getName());
+ ICrossUserService crossUserService = ICrossUserService
+ .Stub.asInterface(service);
+ try {
+ crossUserService.onEnabledCalled();
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error when calling primary user", re);
+ // Do nothing, primary user will time out
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "onServiceDisconnected is called");
+ }
+ };
+ final Intent serviceIntent = new Intent(context, PrimaryUserService.class);
+ assertTrue(dpm.bindDeviceAdminServiceAsUser(
+ getComponentName(context),
+ serviceIntent,
+ serviceConnection,
+ Context.BIND_AUTO_CREATE,
+ target));
+ }
+
+ public static ComponentName getComponentName(Context context) {
+ return new ComponentName(context, SecondaryUserAdminReceiver.class);
+ }
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ICrossUserService.aidl b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ICrossUserService.aidl
new file mode 100644
index 0000000..0cf5dcf
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ICrossUserService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceowner;
+
+interface ICrossUserService {
+ void onEnabledCalled();
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
index 0b783a0..4050f69 100755
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/KeyManagementTest.java
@@ -15,41 +15,52 @@
*/
package com.android.cts.deviceowner;
+import static android.keystore.cts.CertificateUtils.createCertificate;
import static com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
-import static com.android.cts.deviceowner.BaseDeviceOwnerTest.getWho;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
-import android.app.Activity;
import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.keystore.cts.Attestation;
import android.net.Uri;
+import android.security.AttestedKeyPair;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.test.ActivityInstrumentationTestCase2;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.cert.Certificate;
+import java.security.GeneralSecurityException;
import java.security.KeyFactory;
+import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.AssetManager;
+import java.util.List;
+import java.util.Set;
+import javax.security.auth.x500.X500Principal;
public class KeyManagementTest extends ActivityInstrumentationTestCase2<KeyManagementActivity> {
@@ -67,7 +78,7 @@
// Confirm our DeviceOwner is set up
mDevicePolicyManager = (DevicePolicyManager)
getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
- BaseDeviceOwnerTest.assertDeviceOwner(mDevicePolicyManager);
+ assertDeviceOwner(mDevicePolicyManager);
// Hostside test has set a device lockscreen in order to enable credential storage
}
@@ -129,17 +140,23 @@
assertGranted(withhold, false);
}
+ private List<Certificate> loadCertificateChain(String assetName) throws Exception {
+ final Collection<Certificate> certs = loadCertificatesFromAsset(assetName);
+ final ArrayList<Certificate> certChain = new ArrayList(certs);
+ // Some sanity check on the cert chain
+ assertTrue(certs.size() > 1);
+ for (int i = 1; i < certChain.size(); i++) {
+ certChain.get(i - 1).verify(certChain.get(i).getPublicKey());
+ }
+ return certChain;
+ }
+
public void testCanInstallCertChain() throws Exception {
// Use assets/generate-client-cert-chain.sh to regenerate the client cert chain.
final PrivateKey privKey = loadPrivateKeyFromAsset("user-cert-chain.key");
- final Collection<Certificate> certs = loadCertificatesFromAsset("user-cert-chain.crt");
- final Certificate[] certChain = certs.toArray(new Certificate[certs.size()]);
+ final Certificate[] certChain = loadCertificateChain("user-cert-chain.crt")
+ .toArray(new Certificate[0]);
final String alias = "com.android.test.clientkeychain";
- // Some sanity check on the cert chain
- assertTrue(certs.size() > 1);
- for (int i = 1; i < certs.size(); i++) {
- certChain[i - 1].verify(certChain[i].getPublicKey());
- }
// Install keypairs.
assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true));
@@ -215,11 +232,231 @@
}
}
+ public void testNotUserSelectableAliasCanBeChosenViaPolicy() throws Exception {
+ final String alias = "com.android.test.not-selectable-key-1";
+ final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
+ final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
+
+ // Install keypair.
+ assertTrue(mDevicePolicyManager.installKeyPair(
+ getWho(), privKey, new Certificate[] {cert}, alias, false, false));
+ try {
+ // Request and retrieve using the alias.
+ assertGranted(alias, false);
+ assertEquals(alias, new KeyChainAliasFuture(alias).get());
+ assertGranted(alias, true);
+ } finally {
+ // Delete regardless of whether the test succeeded.
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
+ byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
+ byte[] data = new String("hello").getBytes();
+ Signature sign = Signature.getInstance(algoIdentifier);
+ sign.initSign(privateKey);
+ sign.update(data);
+ return sign.sign();
+ }
+
+ void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
+ throws Exception {
+ byte[] data = new String("hello").getBytes();
+ Signature verify = Signature.getInstance(algoIdentifier);
+ verify.initVerify(publicKey);
+ verify.update(data);
+ assertTrue(verify.verify(signature));
+ }
+
+ void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
+ verifySignature(algoIdentifier, keyPair.getPublic(),
+ signDataWithKey(algoIdentifier, keyPair.getPrivate()));
+ }
+
+ public void testCanGenerateRSAKeyPair() throws Exception {
+ final String alias = "com.android.test.generated-rsa-1";
+ try {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setKeySize(2048)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+ .build();
+
+ AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+ getWho(), "RSA", spec, 0);
+ assertNotNull(generated);
+ verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
+ } finally {
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
+ public void testCanGenerateECKeyPair() throws Exception {
+ final String alias = "com.android.test.generated-ec-1";
+ try {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .build();
+
+ AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+ getWho(), "EC", spec, 0);
+ assertNotNull(generated);
+ verifySignatureOverData("SHA256withECDSA", generated.getKeyPair());
+ } finally {
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
+ private void validateAttestationRecord(List<Certificate> attestation,
+ byte[] providedChallenge) throws CertificateParsingException {
+ assertNotNull(attestation);
+ assertTrue(attestation.size() >= 2);
+ X509Certificate leaf = (X509Certificate) attestation.get(0);
+ Attestation attestationRecord = new Attestation(leaf);
+ assertTrue(Arrays.equals(providedChallenge,
+ attestationRecord.getAttestationChallenge()));
+ }
+
+ private void validateSignatureChain(List<Certificate> chain, PublicKey leafKey)
+ throws GeneralSecurityException {
+ X509Certificate leaf = (X509Certificate) chain.get(0);
+ PublicKey keyFromCert = leaf.getPublicKey();
+ assertTrue(Arrays.equals(keyFromCert.getEncoded(), leafKey.getEncoded()));
+ // Check that the certificate chain is valid.
+ for (int i = 1; i < chain.size(); i++) {
+ X509Certificate intermediate = (X509Certificate) chain.get(i);
+ PublicKey intermediateKey = intermediate.getPublicKey();
+ leaf.verify(intermediateKey);
+ leaf = intermediate;
+ }
+
+ // leaf is now the root, verify the root is self-signed.
+ PublicKey rootKey = leaf.getPublicKey();
+ leaf.verify(rootKey);
+ }
+
+ public void testCanGenerateECKeyPairWithKeyAttestation() throws Exception {
+ final String alias = "com.android.test.attested-ec-1";
+ byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+ try {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAttestationChallenge(attestationChallenge)
+ .build();
+ AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+ getWho(), "EC", spec, 0);
+ assertNotNull(generated);
+ final KeyPair keyPair = generated.getKeyPair();
+ final String algorithmIdentifier = "SHA256withECDSA";
+ verifySignatureOverData(algorithmIdentifier, keyPair);
+ List<Certificate> attestation = generated.getAttestationRecord();
+ validateAttestationRecord(attestation, attestationChallenge);
+ validateSignatureChain(attestation, keyPair.getPublic());
+ } finally {
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
+ public void testCanGenerateECKeyPairWithDeviceIdAttestation() throws Exception {
+ final String alias = "com.android.test.devid-attested-ec-1";
+ byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+ try {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAttestationChallenge(attestationChallenge)
+ .build();
+ AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+ getWho(), "EC", spec, ID_TYPE_SERIAL);
+ if (generated == null) {
+ // Since supporting Device ID attestation is optional, do not fail the test if no
+ // attestation record was generated.
+ return;
+ }
+ final KeyPair keyPair = generated.getKeyPair();
+ verifySignatureOverData("SHA256withECDSA", keyPair);
+ List<Certificate> attestation = generated.getAttestationRecord();
+ validateAttestationRecord(attestation, attestationChallenge);
+ validateSignatureChain(attestation, keyPair.getPublic());
+ } finally {
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
+ public void testCanSetKeyPairCert() throws Exception {
+ final String alias = "com.android.test.set-ec-1";
+ try {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .build();
+
+ AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+ getWho(), "EC", spec, 0);
+ assertNotNull(generated);
+ // Create a self-signed cert to go with it.
+ X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
+ X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
+ X509Certificate cert = createCertificate(generated.getKeyPair(), subject, issuer);
+ // Set the certificate chain
+ List<Certificate> certs = new ArrayList<Certificate>();
+ certs.add(cert);
+ mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, certs, true);
+ // Make sure that the alias can now be obtained.
+ assertEquals(alias, new KeyChainAliasFuture(alias).get());
+ // And can be retrieved from KeyChain
+ X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(getActivity(), alias);
+ assertEquals(fetchedCerts.length, certs.size());
+ assertTrue(Arrays.equals(fetchedCerts[0].getEncoded(), certs.get(0).getEncoded()));
+ } finally {
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
+ public void testCanSetKeyPairCertChain() throws Exception {
+ final String alias = "com.android.test.set-ec-2";
+ try {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ alias,
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .build();
+
+ AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+ getWho(), "EC", spec, 0);
+ assertNotNull(generated);
+ List<Certificate> chain = loadCertificateChain("user-cert-chain.crt");
+ mDevicePolicyManager.setKeyPairCertificate(getWho(), alias, chain, true);
+ // Make sure that the alias can now be obtained.
+ assertEquals(alias, new KeyChainAliasFuture(alias).get());
+ // And can be retrieved from KeyChain
+ X509Certificate[] fetchedCerts = KeyChain.getCertificateChain(getActivity(), alias);
+ assertEquals(fetchedCerts.length, chain.size());
+ for (int i = 0; i < chain.size(); i++) {
+ assertTrue(Arrays.equals(fetchedCerts[i].getEncoded(), chain.get(i).getEncoded()));
+ }
+ } finally {
+ assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+ }
+ }
+
private void assertGranted(String alias, boolean expected) throws InterruptedException {
boolean granted = false;
try {
granted = (KeyChain.getPrivateKey(getActivity(), alias) != null);
} catch (KeyChainException e) {
+ if (expected) {
+ e.printStackTrace();
+ }
}
assertEquals("Grant for alias: \"" + alias + "\"", expected, granted);
}
@@ -289,4 +526,15 @@
return mChosenAlias;
}
}
+
+ private void assertDeviceOwner(DevicePolicyManager devicePolicyManager) {
+ assertNotNull(devicePolicyManager);
+ assertTrue(devicePolicyManager.isAdminActive(getWho()));
+ assertTrue(devicePolicyManager.isDeviceOwnerApp(getActivity().getPackageName()));
+ assertFalse(devicePolicyManager.isManagedProfile(getWho()));
+ }
+
+ private ComponentName getWho() {
+ return BasicAdminReceiver.getComponentName(getActivity());
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java
index ce79922..f9f9a33 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskHostDrivenTest.java
@@ -45,10 +45,6 @@
private static final String TAG = LockTaskHostDrivenTest.class.getName();
- private static final String PACKAGE_NAME = LockTaskHostDrivenTest.class.getPackage().getName();
- private static final ComponentName ADMIN_COMPONENT =
- new ComponentName(PACKAGE_NAME, BaseDeviceOwnerTest.BasicAdminReceiver.class.getName());
-
private static final String LOCK_TASK_ACTIVITY
= LockTaskUtilityActivityIfWhitelisted.class.getName();
@@ -60,7 +56,7 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
@@ -96,9 +92,11 @@
@Test
public void clearDefaultHomeIntentReceiver() {
- mDevicePolicyManager.clearPackagePersistentPreferredActivities(ADMIN_COMPONENT,
- PACKAGE_NAME);
- mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
+ mDevicePolicyManager.clearPackagePersistentPreferredActivities(
+ BasicAdminReceiver.getComponentName(mContext),
+ mContext.getPackageName());
+ mDevicePolicyManager.setLockTaskPackages(BasicAdminReceiver.getComponentName(mContext),
+ new String[0]);
}
private void checkLockedActivityIsRunning() throws Exception {
@@ -113,19 +111,20 @@
}
private void launchLockTaskActivity() {
- Intent intent = new Intent();
- intent.setClassName(PACKAGE_NAME, LOCK_TASK_ACTIVITY);
+ Intent intent = new Intent(mContext, LockTaskUtilityActivityIfWhitelisted.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(LockTaskUtilityActivity.START_LOCK_TASK, true);
mContext.startActivity(intent);
}
private void setDefaultHomeIntentReceiver() {
- mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
+ mDevicePolicyManager.setLockTaskPackages(BasicAdminReceiver.getComponentName(mContext),
+ new String[]{mContext.getPackageName()});
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MAIN);
intentFilter.addCategory(Intent.CATEGORY_HOME);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
- mDevicePolicyManager.addPersistentPreferredActivity(ADMIN_COMPONENT, intentFilter,
- new ComponentName(PACKAGE_NAME, LOCK_TASK_ACTIVITY));
+ mDevicePolicyManager.addPersistentPreferredActivity(
+ BasicAdminReceiver.getComponentName(mContext), intentFilter,
+ new ComponentName(mContext.getPackageName(), LOCK_TASK_ACTIVITY));
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
index 9c5380a..6ef2ba1 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
@@ -15,14 +15,22 @@
*/
package com.android.cts.deviceowner;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertArrayEquals;
-import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -39,6 +47,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
public class LockTaskTest {
@@ -46,7 +56,7 @@
private static final String PACKAGE_NAME = LockTaskTest.class.getPackage().getName();
private static final ComponentName ADMIN_COMPONENT =
- new ComponentName(PACKAGE_NAME, BaseDeviceOwnerTest.BasicAdminReceiver.class.getName());
+ new ComponentName(PACKAGE_NAME, BasicAdminReceiver.class.getName());
private static final String TEST_PACKAGE = "com.google.android.example.somepackage";
private static final String UTILITY_ACTIVITY
@@ -54,18 +64,23 @@
private static final String UTILITY_ACTIVITY_IF_WHITELISTED
= "com.android.cts.deviceowner.LockTaskUtilityActivityIfWhitelisted";
- private static final String RECEIVING_ACTIVITY_PACKAGE_NAME
- = "com.android.cts.intent.receiver";
- private static final String RECEIVING_ACTIVITY_NAME
- = "com.android.cts.intent.receiver.IntentReceiverActivity";
+ private static final String RECEIVER_ACTIVITY_PACKAGE_NAME =
+ "com.android.cts.intent.receiver";
+ private static final String RECEIVER_ACTIVITY_NAME =
+ "com.android.cts.intent.receiver.IntentReceiverActivity";
private static final String ACTION_JUST_CREATE =
"com.android.cts.action.JUST_CREATE";
+ private static final String ACTION_CREATE_AND_WAIT =
+ "com.android.cts.action.CREATE_AND_WAIT";
+ private static final String RECEIVER_ACTIVITY_CREATED_ACTION =
+ "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_CREATED";
+ private static final String RECEIVER_ACTIVITY_DESTROYED_ACTION =
+ "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_DESTROYED";
- private static final int ACTIVITY_RESUMED_TIMEOUT_MILLIS = 20000; // 20 seconds
- private static final int ACTIVITY_RUNNING_TIMEOUT_MILLIS = 10000; // 10 seconds
- private static final int ACTIVITY_DESTROYED_TIMEOUT_MILLIS = 60000; // 60 seconds
- public static final String RECEIVING_ACTIVITY_CREATED_ACTION
- = "com.android.cts.deviceowner.RECEIVING_ACTIVITY_CREATED_ACTION";
+ private static final long ACTIVITY_RESUMED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
+ private static final long ACTIVITY_RUNNING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+ private static final long ACTIVITY_DESTROYED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
+
/**
* The tests below need to keep detailed track of the state of the activity
* that is started and stopped frequently. To do this it sends a number of
@@ -105,31 +120,27 @@
mIntentHandled = true;
LockTaskTest.this.notify();
}
- } else if (RECEIVING_ACTIVITY_CREATED_ACTION.equals(action)) {
- synchronized(mReceivingActivityCreatedLock) {
- mReceivingActivityWasCreated = true;
- mReceivingActivityCreatedLock.notify();
+ } else if (RECEIVER_ACTIVITY_CREATED_ACTION.equals(action)) {
+ synchronized(mReceiverActivityRunningLock) {
+ mIsReceiverActivityRunning = true;
+ mReceiverActivityRunningLock.notify();
+ }
+ } else if (RECEIVER_ACTIVITY_DESTROYED_ACTION.equals(action)) {
+ synchronized (mReceiverActivityRunningLock) {
+ mIsReceiverActivityRunning = false;
+ mReceiverActivityRunningLock.notify();
}
}
}
};
- public static class IntentReceivingActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- sendBroadcast(new Intent(RECEIVING_ACTIVITY_CREATED_ACTION));
- finish();
- }
- }
-
private volatile boolean mIsActivityRunning;
private volatile boolean mIsActivityResumed;
- private volatile boolean mReceivingActivityWasCreated;
+ private volatile boolean mIsReceiverActivityRunning;
private volatile boolean mIntentHandled;
private final Object mActivityRunningLock = new Object();
private final Object mActivityResumedLock = new Object();
- private final Object mReceivingActivityCreatedLock = new Object();
+ private final Object mReceiverActivityRunningLock = new Object();
private Context mContext;
private ActivityManager mActivityManager;
@@ -139,17 +150,17 @@
public void setUp() {
mContext = InstrumentationRegistry.getContext();
- mDevicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[0]);
- mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
IntentFilter filter = new IntentFilter();
filter.addAction(LockTaskUtilityActivity.CREATE_ACTION);
filter.addAction(LockTaskUtilityActivity.DESTROY_ACTION);
filter.addAction(LockTaskUtilityActivity.INTENT_ACTION);
filter.addAction(LockTaskUtilityActivity.RESUME_ACTION);
filter.addAction(LockTaskUtilityActivity.PAUSE_ACTION);
- filter.addAction(RECEIVING_ACTIVITY_CREATED_ACTION);
+ filter.addAction(RECEIVER_ACTIVITY_CREATED_ACTION);
+ filter.addAction(RECEIVER_ACTIVITY_DESTROYED_ACTION);
mContext.registerReceiver(mReceiver, filter);
}
@@ -172,6 +183,34 @@
assertFalse(mDevicePolicyManager.isLockTaskPermitted(TEST_PACKAGE));
}
+ // Setting and unsetting the lock task features. The actual UI behavior is tested with CTS
+ // verifier.
+ @Test
+ public void testSetLockTaskFeatures() {
+ final int[] flags = new int[] {
+ LOCK_TASK_FEATURE_SYSTEM_INFO,
+ LOCK_TASK_FEATURE_NOTIFICATIONS,
+ LOCK_TASK_FEATURE_HOME,
+ LOCK_TASK_FEATURE_RECENTS,
+ LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+ LOCK_TASK_FEATURE_KEYGUARD
+ };
+
+ int cumulative = LOCK_TASK_FEATURE_NONE;
+ for (int flag : flags) {
+ mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, flag);
+ assertEquals(flag, mDevicePolicyManager.getLockTaskFeatures(ADMIN_COMPONENT));
+
+ cumulative |= flag;
+ mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, cumulative);
+ assertEquals(cumulative, mDevicePolicyManager.getLockTaskFeatures(ADMIN_COMPONENT));
+
+ mDevicePolicyManager.setLockTaskFeatures(ADMIN_COMPONENT, LOCK_TASK_FEATURE_NONE);
+ assertEquals(LOCK_TASK_FEATURE_NONE,
+ mDevicePolicyManager.getLockTaskFeatures(ADMIN_COMPONENT));
+ }
+ }
+
// Start lock task, verify that ActivityManager knows thats what is going on.
@Test
public void testStartLockTask() throws Exception {
@@ -219,6 +258,40 @@
assertFalse(mIsActivityResumed);
}
+ // Verifies that removing the whitelist authorization immediately finishes the corresponding
+ // locked task. The other locked task(s) should remain locked.
+ @Test
+ public void testUpdateWhitelisting_twoTasks() throws Exception {
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME,
+ RECEIVER_ACTIVITY_PACKAGE_NAME});
+
+ // Start first locked task
+ startLockTask(UTILITY_ACTIVITY);
+ waitForResume();
+
+ // Start the other task from the running activity
+ mIsReceiverActivityRunning = false;
+ Intent launchIntent = createReceiverActivityIntent(true /*newTask*/, true /*shouldWait*/);
+ mContext.startActivity(launchIntent);
+ synchronized (mReceiverActivityRunningLock) {
+ mReceiverActivityRunningLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+ assertTrue(mIsReceiverActivityRunning);
+ }
+
+ // Remove whitelist authorization of the second task
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
+ synchronized (mReceiverActivityRunningLock) {
+ mReceiverActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
+ assertFalse(mIsReceiverActivityRunning);
+ }
+
+ assertLockTaskModeActive();
+ assertTrue(mIsActivityRunning);
+ assertTrue(mIsActivityResumed);
+
+ stopAndFinish(UTILITY_ACTIVITY);
+ }
+
// This launches an activity that is in the current task.
// This should always be permitted as a part of lock task (since it isn't a new task).
@Test
@@ -227,15 +300,15 @@
startLockTask(UTILITY_ACTIVITY);
waitForResume();
- mReceivingActivityWasCreated = false;
- Intent launchIntent = getIntentReceivingActivityIntent(0);
+ mIsReceiverActivityRunning = false;
+ Intent launchIntent = createReceiverActivityIntent(false /*newTask*/, false /*shouldWait*/);
Intent lockTaskUtility = getLockTaskUtility(UTILITY_ACTIVITY);
lockTaskUtility.putExtra(LockTaskUtilityActivity.START_ACTIVITY, launchIntent);
mContext.startActivity(lockTaskUtility);
- synchronized (mReceivingActivityCreatedLock) {
- mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
- assertTrue(mReceivingActivityWasCreated);
+ synchronized (mReceiverActivityRunningLock) {
+ mReceiverActivityRunningLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+ assertTrue(mIsReceiverActivityRunning);
}
stopAndFinish(UTILITY_ACTIVITY);
}
@@ -245,17 +318,16 @@
@Test
public void testStartActivity_outsideTaskWhitelisted() throws Exception {
mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME,
- RECEIVING_ACTIVITY_PACKAGE_NAME });
+ RECEIVER_ACTIVITY_PACKAGE_NAME});
startLockTask(UTILITY_ACTIVITY);
waitForResume();
- mReceivingActivityWasCreated = false;
- Intent launchIntent = getIntentReceivingActivityIntent(0);
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mIsReceiverActivityRunning = false;
+ Intent launchIntent = createReceiverActivityIntent(true /*newTask*/, false /*shouldWait*/);
mContext.startActivity(launchIntent);
- synchronized (mReceivingActivityCreatedLock) {
- mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
- assertTrue(mReceivingActivityWasCreated);
+ synchronized (mReceiverActivityRunningLock) {
+ mReceiverActivityRunningLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+ assertTrue(mIsReceiverActivityRunning);
}
stopAndFinish(UTILITY_ACTIVITY);
}
@@ -268,11 +340,11 @@
startLockTask(UTILITY_ACTIVITY);
waitForResume();
- Intent launchIntent = getIntentReceivingActivityIntent(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Intent launchIntent = createReceiverActivityIntent(true /*newTask*/, false /*shouldWait*/);
mContext.startActivity(launchIntent);
synchronized (mActivityResumedLock) {
mActivityResumedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
- assertFalse(mReceivingActivityWasCreated);
+ assertFalse(mIsReceiverActivityRunning);
}
stopAndFinish(UTILITY_ACTIVITY);
}
@@ -341,6 +413,27 @@
assertFalse(mIsActivityResumed);
}
+ // Start lock task with ActivityOptions
+ @Test
+ public void testActivityOptions_whitelisted() throws Exception {
+ mDevicePolicyManager.setLockTaskPackages(ADMIN_COMPONENT, new String[] { PACKAGE_NAME });
+ startLockTaskWithOptions(UTILITY_ACTIVITY);
+ waitForResume();
+
+ // Verify that activity open and activity manager is in lock task.
+ assertLockTaskModeActive();
+ assertTrue(mIsActivityRunning);
+ assertTrue(mIsActivityResumed);
+
+ stopAndFinish(UTILITY_ACTIVITY);
+ }
+
+ // Starting a non-whitelisted activity with ActivityOptions is not allowed
+ @Test(expected = SecurityException.class)
+ public void testActivityOptions_nonWhitelisted() throws Exception {
+ startLockTaskWithOptions(UTILITY_ACTIVITY);
+ }
+
/**
* Checks that lock task mode is active and fails the test if it isn't.
*/
@@ -412,6 +505,15 @@
}
/**
+ * Starts LockTaskUtilityActivity with {@link ActivityOptions#setLockTaskMode(boolean)}
+ */
+ private void startLockTaskWithOptions(String className) throws InterruptedException {
+ Intent intent = getLockTaskUtility(className);
+ Bundle options = ActivityOptions.makeBasic().setLockTaskMode(true).toBundle();
+ startAndWait(intent, options);
+ }
+
+ /**
* Calls stopLockTask on the LockTaskUtilityActivity
*/
private void stopLockTask(String className) throws InterruptedException {
@@ -435,9 +537,16 @@
* the command.
*/
private void startAndWait(Intent intent) throws InterruptedException {
+ startAndWait(intent, null);
+ }
+
+ /**
+ * Same as {@link #startAndWait(Intent)}, but with additional {@link ActivityOptions}.
+ */
+ private void startAndWait(Intent intent, Bundle options) throws InterruptedException {
mIntentHandled = false;
synchronized (this) {
- mContext.startActivity(intent);
+ mContext.startActivity(intent, options);
// Give 20 secs to finish.
wait(ACTIVITY_RUNNING_TIMEOUT_MILLIS);
assertTrue(mIntentHandled);
@@ -456,12 +565,13 @@
return intent;
}
- private Intent getIntentReceivingActivityIntent(int flags) {
- Intent intent = new Intent();
+ /** Create an intent to launch {@link #RECEIVER_ACTIVITY_NAME}. */
+ private Intent createReceiverActivityIntent(boolean newTask, boolean shouldWait) {
+ final Intent intent = new Intent();
intent.setComponent(
- new ComponentName(RECEIVING_ACTIVITY_PACKAGE_NAME, RECEIVING_ACTIVITY_NAME));
- intent.setAction(ACTION_JUST_CREATE);
- intent.setFlags(flags);
+ new ComponentName(RECEIVER_ACTIVITY_PACKAGE_NAME, RECEIVER_ACTIVITY_NAME));
+ intent.setAction(shouldWait ? ACTION_CREATE_AND_WAIT : ACTION_JUST_CREATE);
+ intent.setFlags(newTask ? Intent.FLAG_ACTIVITY_NEW_TASK : 0);
return intent;
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index 6b62e88..cb723d5 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.support.test.InstrumentationRegistry;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
@@ -36,6 +37,7 @@
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -43,6 +45,7 @@
public class NetworkLoggingTest extends BaseDeviceOwnerTest {
private static final String TAG = "NetworkLoggingTest";
+ private static final String ARG_BATCH_COUNT = "batchCount";
private static final int FAKE_BATCH_TOKEN = -666; // real batch tokens are always non-negative
private static final int FULL_LOG_BATCH_SIZE = 1200;
private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.deviceowner";
@@ -71,20 +74,28 @@
@Override
public void onReceive(Context context, Intent intent) {
- if (BaseDeviceOwnerTest.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
- mGenerateNetworkTraffic = false;
- mCurrentBatchToken = intent.getLongExtra(
- BaseDeviceOwnerTest.EXTRA_NETWORK_LOGS_BATCH_TOKEN, FAKE_BATCH_TOKEN);
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
+ if (BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
+ long token =
+ intent.getLongExtra(BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN,
+ FAKE_BATCH_TOKEN);
+ // Retrieve network logs.
+ final List<NetworkEvent> events = mDevicePolicyManager.retrieveNetworkLogs(getWho(),
+ token);
+ if (events == null) {
+ fail("Failed to retrieve batch of network logs with batch token " + token);
+ return;
}
+ if (mBatchCountDown.getCount() > 0) {
+ mNetworkEvents.addAll(events);
+ }
+ mBatchCountDown.countDown();
}
}
};
- private CountDownLatch mCountDownLatch;
- private long mCurrentBatchToken;
- private volatile boolean mGenerateNetworkTraffic;
+ private CountDownLatch mBatchCountDown;
+ private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
+ private int mBatchesRequested = 1;
@Override
protected void tearDown() throws Exception {
@@ -124,12 +135,11 @@
* traffic, so that the batch of logs is created
*/
public void testNetworkLoggingAndRetrieval() throws Exception {
- mCountDownLatch = new CountDownLatch(1);
- mCurrentBatchToken = FAKE_BATCH_TOKEN;
- mGenerateNetworkTraffic = true;
+ mBatchesRequested = InstrumentationRegistry.getArguments().getInt(ARG_BATCH_COUNT, 1);
+ mBatchCountDown = new CountDownLatch(mBatchesRequested);
// register a receiver that listens for DeviceAdminReceiver#onNetworkLogsAvailable()
final IntentFilter filterNetworkLogsAvailable = new IntentFilter(
- BaseDeviceOwnerTest.ACTION_NETWORK_LOGS_AVAILABLE);
+ BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE);
LocalBroadcastManager.getInstance(mContext).registerReceiver(mNetworkLogsReceiver,
filterNetworkLogsAvailable);
@@ -144,36 +154,41 @@
// TODO: here test that facts about logging are shown in the UI
+ // Fetch and verify the batches of events.
+ generateBatches();
+ }
+
+ private void generateBatches() throws Exception {
// visit websites to verify their dns lookups are logged
for (final String url : LOGGED_URLS_LIST) {
connectToWebsite(url);
}
- // generate enough traffic to fill a batch.
- int dummyReqNo = generateDummyTraffic();
+ // generate enough traffic to fill the batches.
+ int dummyReqNo = 0;
+ for (int i = 0; i < mBatchesRequested; i++) {
+ dummyReqNo += generateDummyTraffic();
+ }
// if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
- // 3 minutes just in case
- mCountDownLatch.await(3, TimeUnit.MINUTES);
+ // 3 minutes per batch just in case
+ int timeoutMins = 3 * mBatchesRequested;
+ mBatchCountDown.await(timeoutMins, TimeUnit.MINUTES);
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mNetworkLogsReceiver);
- if (mGenerateNetworkTraffic) {
- fail("Carried out 100 iterations and waited for 3 minutes, but still didn't get"
+ if (mBatchCountDown.getCount() > 0) {
+ fail("Generated events for " + mBatchesRequested + " batches and waited for "
+ + timeoutMins + " minutes, but still didn't get"
+ " DeviceAdminReceiver#onNetworkLogsAvailable() callback");
}
- // retrieve and verify network logs
- final List<NetworkEvent> networkEvents = mDevicePolicyManager.retrieveNetworkLogs(getWho(),
- mCurrentBatchToken);
- if (networkEvents == null) {
- fail("Failed to retrieve batch of network logs with batch token " + mCurrentBatchToken);
- return;
- }
-
+ // Verify network logs.
+ assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
// For each of the real URLs we have two events: one DNS and one connect. Dummy requests
// don't require DNS queries.
int eventsExpected =
- Math.min(FULL_LOG_BATCH_SIZE, 2 * LOGGED_URLS_LIST.length + dummyReqNo);
- verifyNetworkLogs(networkEvents, eventsExpected);
+ Math.min(FULL_LOG_BATCH_SIZE * mBatchesRequested,
+ 2 * LOGGED_URLS_LIST.length + dummyReqNo);
+ verifyNetworkLogs(mNetworkEvents, eventsExpected);
}
private void verifyNetworkLogs(List<NetworkEvent> networkEvents, int eventsExpected) {
@@ -189,6 +204,10 @@
if (i > 0) {
assertTrue(currentEvent.getTimestamp() >= networkEvents.get(i - 1).getTimestamp());
}
+ // verify that the event IDs are monotonically increasing
+ if (i > 0) {
+ assertTrue(currentEvent.getId() == (networkEvents.get(i - 1).getId() + 1));
+ }
// count how many events come from the CTS app
if (CTS_APP_PACKAGE_NAME.equals(currentEvent.getPackageName())) {
ctsPackageNameCounter++;
@@ -270,8 +289,8 @@
private int makeDummyRequests(int port) {
int reqNo;
- final String DUMMY_SERVER = "127.0.0.1:" + port + "";
- for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mGenerateNetworkTraffic; reqNo++) {
+ final String DUMMY_SERVER = "127.0.0.1:" + port;
+ for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mBatchCountDown.getCount() > 0; reqNo++) {
connectToWebsite(DUMMY_SERVER);
try {
// Just to prevent choking the server.
@@ -303,7 +322,7 @@
output.flush();
output.close();
} catch (IOException e) {
- if (mGenerateNetworkTraffic) {
+ if (mBatchCountDown.getCount() > 0) {
Log.w(TAG, "Failed to serve connection", e);
} else {
break;
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
new file mode 100644
index 0000000..5280e06
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
@@ -0,0 +1,140 @@
+package com.android.cts.deviceowner;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+
+/**
+ * Test case for package install and uninstall.
+ */
+public class PackageInstallTest extends BaseAffiliatedProfileOwnerTest {
+ private static final String TEST_APP_LOCATION =
+ "/data/local/tmp/cts/packageinstaller/CtsEmptyTestApp.apk";
+ private static final String TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
+ private static final int REQUEST_CODE = 0;
+ private static final String ACTION_INSTALL_COMMIT =
+ "com.android.cts.deviceowner.INTENT_PACKAGE_INSTALL_COMMIT";
+ private static final int PACKAGE_INSTALLER_STATUS_UNDEFINED = -1000;
+
+ private PackageManager mPackageManager;
+ private PackageInstaller mPackageInstaller;
+ private PackageInstaller.Session mSession;
+ private BlockingBroadcastReceiver mBroadcastReceiver;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPackageManager = mContext.getPackageManager();
+ mPackageInstaller = mPackageManager.getPackageInstaller();
+ assertNotNull(mPackageInstaller);
+
+ mBroadcastReceiver = new BlockingBroadcastReceiver(mContext, ACTION_INSTALL_COMMIT);
+ mBroadcastReceiver.register();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mBroadcastReceiver.unregisterQuietly();
+ if (mSession != null) {
+ mSession.abandon();
+ }
+
+ super.tearDown();
+ }
+
+ public void testPackageInstall() throws Exception {
+ assertFalse(isPackageInstalled(TEST_APP_PKG));
+
+ // Install the package.
+ installPackage(TEST_APP_LOCATION);
+
+ Intent intent = mBroadcastReceiver.awaitForBroadcast();
+ assertNotNull(intent);
+ assertEquals(PackageInstaller.STATUS_SUCCESS,
+ intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PACKAGE_INSTALLER_STATUS_UNDEFINED));
+ assertEquals(TEST_APP_PKG, intent.getStringExtra(
+ PackageInstaller.EXTRA_PACKAGE_NAME));
+ assertTrue(isPackageInstalled(TEST_APP_PKG));
+ }
+
+ public void testPackageUninstall() throws Exception {
+ assertTrue(isPackageInstalled(TEST_APP_PKG));
+
+ // Uninstall the package.
+ mPackageInstaller.uninstall(TEST_APP_PKG, getCommitCallback());
+
+ Intent intent = mBroadcastReceiver.awaitForBroadcast();
+ assertNotNull(intent);
+ assertEquals(PackageInstaller.STATUS_SUCCESS,
+ intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PACKAGE_INSTALLER_STATUS_UNDEFINED));
+ assertEquals(TEST_APP_PKG, intent.getStringExtra(
+ PackageInstaller.EXTRA_PACKAGE_NAME));
+ assertFalse(isPackageInstalled(TEST_APP_PKG));
+ }
+
+ public void testKeepPackageCache() throws Exception {
+ // Set keep package cache.
+ mDevicePolicyManager.setKeepUninstalledPackages(getWho(),
+ Collections.singletonList(TEST_APP_PKG));
+ }
+
+ public void testInstallExistingPackage() throws Exception {
+ assertFalse(isPackageInstalled(TEST_APP_PKG));
+
+ // Install the existing package.
+ assertTrue(mDevicePolicyManager.installExistingPackage(getWho(), TEST_APP_PKG));
+ assertTrue(isPackageInstalled(TEST_APP_PKG));
+ }
+
+ private void installPackage(String packageLocation) throws Exception {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(TEST_APP_PKG);
+ int sessionId = mPackageInstaller.createSession(params);
+ mSession = mPackageInstaller.openSession(sessionId);
+
+ File file = new File(packageLocation);
+ InputStream in = new FileInputStream(file);
+ OutputStream out = mSession.openWrite("PackageInstallTest", 0, file.length());
+ byte[] buffer = new byte[65536];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ out.write(buffer, 0, c);
+ }
+ mSession.fsync(out);
+ out.close();
+ mSession.commit(getCommitCallback());
+ mSession.close();
+ }
+
+ private IntentSender getCommitCallback() {
+ // Create a PendingIntent and use it to generate the IntentSender
+ Intent broadcastIntent = new Intent(ACTION_INSTALL_COMMIT);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext,
+ REQUEST_CODE,
+ broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ return pendingIntent.getIntentSender();
+ }
+
+ private boolean isPackageInstalled(String packageName) {
+ try {
+ return mPackageManager.getPackageInfo(packageName, 0) != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
index 201f382..bc4fe8d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SecurityLoggingTest.java
@@ -17,13 +17,14 @@
import android.app.admin.SecurityLog.SecurityEvent;
import android.os.Parcel;
-import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.TimeUnit;
public class SecurityLoggingTest extends BaseDeviceOwnerTest {
+ private static final String ARG_BATCH_NUMBER = "batchNumber";
+ private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
/**
* Test: retrieving security logs can only be done if there's one user on the device or all
@@ -56,14 +57,27 @@
*/
public void testGetSecurityLogs() {
List<SecurityEvent> events = mDevicePolicyManager.retrieveSecurityLogs(getWho());
+ String param = InstrumentationRegistry.getArguments().getString(ARG_BATCH_NUMBER);
+ int batchNumber = param == null ? 0 : Integer.parseInt(param);
+ verifySecurityLogs(batchNumber, events);
+ }
- // There must be at least some events, e.g. PackageManager logs all process launches.
+ private static void verifySecurityLogs(int batchNumber, List<SecurityEvent> events) {
assertTrue("Unable to get events", events != null && events.size() > 0);
-
+ assertTrue(
+ "First id in batch " + events.get(0).getId() + " is too small for the batch number "
+ + batchNumber,
+ events.get(0).getId() >= (BUFFER_ENTRIES_NOTIFICATION_LEVEL * batchNumber));
// We don't know much about the events, so just call public API methods.
for (int i = 0; i < events.size(); i++) {
SecurityEvent event = events.get(i);
+ // Test id for monotonically increasing.
+ if (i > 0) {
+ assertEquals("Event IDs are not monotonically increasing within the batch",
+ events.get(i - 1).getId() + 1, event.getId());
+ }
+
// Test parcelling: flatten to a parcel.
Parcel p = Parcel.obtain();
event.writeToParcel(p, 0);
@@ -81,6 +95,8 @@
assertEquals("Parcelling changed the result of getData",
event.getData(), restored.getData());
}
+ assertEquals("Parcelling changed the result of getId",
+ event.getId(), restored.getId());
assertEquals("Parcelling changed the result of getTag",
event.getTag(), restored.getTag());
assertEquals("Parcelling changed the result of getTimeNanos",
@@ -114,7 +130,7 @@
* Test: retrieving security logs should be rate limited - subsequent attempts should return
* null.
*/
- public void testRetrievingSecurityLogsNotPossibleImmediatelyAfterPreviousSuccessfulRetrieval() {
+ public void testSecurityLoggingRetrievalRateLimited() {
List<SecurityEvent> logs = mDevicePolicyManager.retrieveSecurityLogs(getWho());
// if logs is null it means that that attempt was rate limited => test PASS
if (logs != null) {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java
index 028bf2e..514b9e8 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/SetPolicyActivity.java
@@ -63,7 +63,7 @@
getSystemService(Context.DEVICE_POLICY_SERVICE);
String command = intent.getStringExtra(EXTRA_COMMAND);
Log.i(TAG, "Command: \"" + command);
- ComponentName admin = BaseDeviceOwnerTest.getWho();
+ ComponentName admin = BasicAdminReceiver.getComponentName(this);
if (ADD_RESTRICTION_COMMAND.equals(command)) {
String restrictionKey = intent.getStringExtra(EXTRA_RESTRICTION_KEY);
dpm.addUserRestriction(admin, restrictionKey);
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk b/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
index 08edf44..e475215 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/Android.mk
@@ -24,12 +24,11 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v4 \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index da0e16c..84c5de6 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -35,6 +35,7 @@
<action android:name="com.android.cts.action.NOTIFY_URI_CHANGE"/>
<action android:name="com.android.cts.action.OBSERVE_URI_CHANGE"/>
<action android:name="com.android.cts.action.JUST_CREATE" />
+ <action android:name="com.android.cts.action.CREATE_AND_WAIT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/ClearApplicationDataTest.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/ClearApplicationDataTest.java
new file mode 100644
index 0000000..0d2a990
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/ClearApplicationDataTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.intent.receiver;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class that writes to shared preference and verifies that the shared preference gets cleared
+ * after DPM.clearApplicationUserData was called.
+ */
+@SmallTest
+public class ClearApplicationDataTest {
+ private static final String SHARED_PREFERENCE_NAME = "test-preference";
+ private static final String I_WAS_HERE = "I-Was-Here";
+
+ private Context mContext;
+ private SharedPreferences mSharedPrefs;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mSharedPrefs = mContext.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
+ }
+
+ @Test
+ public void testWriteToSharedPreference() {
+ mSharedPrefs.edit().putBoolean(I_WAS_HERE, true).commit();
+ assertTrue(mSharedPrefs.contains(I_WAS_HERE));
+ }
+
+ @Test
+ public void testSharedPreferenceCleared() {
+ assertFalse(mSharedPrefs.contains(I_WAS_HERE));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java
index a645a87..5f04be0 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/IntentReceiverActivity.java
@@ -57,8 +57,14 @@
private static final String ACTION_JUST_CREATE =
"com.android.cts.action.JUST_CREATE";
- public static final String RECEIVING_ACTIVITY_CREATED_ACTION
- = "com.android.cts.deviceowner.RECEIVING_ACTIVITY_CREATED_ACTION";
+ private static final String ACTION_CREATE_AND_WAIT =
+ "com.android.cts.action.CREATE_AND_WAIT";
+
+ private static final String RECEIVER_ACTIVITY_CREATED_ACTION =
+ "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_CREATED";
+
+ private static final String RECEIVER_ACTIVITY_DESTROYED_ACTION =
+ "com.android.cts.deviceowner.action.RECEIVER_ACTIVITY_DESTROYED";
public static final String ACTION_NOTIFY_URI_CHANGE
= "com.android.cts.action.NOTIFY_URI_CHANGE";
@@ -131,10 +137,19 @@
getContentResolver().unregisterContentObserver(uriObserver);
handlerThread.quit();
}
- } else if (ACTION_JUST_CREATE.equals(action)) {
- sendBroadcast(new Intent(RECEIVING_ACTIVITY_CREATED_ACTION));
+ } else if (ACTION_JUST_CREATE.equals(action) || ACTION_CREATE_AND_WAIT.equals(action)) {
+ sendBroadcast(new Intent(RECEIVER_ACTIVITY_CREATED_ACTION));
}
- finish();
+
+ if (!ACTION_CREATE_AND_WAIT.equals(action)) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ sendBroadcast(new Intent(RECEIVER_ACTIVITY_DESTROYED_ACTION));
+ super.onDestroy();
}
private class UriObserver extends ContentObserver {
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.mk b/hostsidetests/devicepolicy/app/IntentSender/Android.mk
index b71ddfb..bb1f6cc 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.mk
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.mk
@@ -24,13 +24,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v4 \
ctstestrunner \
- ub-uiautomator \
- legacy-android-test
+ ub-uiautomator
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
index ed4943f..85e369b 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
@@ -24,13 +24,15 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := legacy-android-test cts-junit
+LOCAL_JAVA_LIBRARIES := cts-junit android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES = \
android-support-v4 \
ctstestrunner \
android-support-test \
- legacy-android-test
+ compatibility-device-util \
+ ShortcutManagerTestUtils \
+ testng
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
new file mode 100644
index 0000000..0349594
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.launchertests;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test that runs {@link UserManager#trySetQuietModeEnabled(boolean, UserHandle)} API
+ * against valid target user.
+ */
+@RunWith(AndroidJUnit4.class)
+public class QuietModeTest {
+ private static final String PARAM_TARGET_USER = "TARGET_USER";
+ private static final String PARAM_ORIGINAL_DEFAULT_LAUNCHER = "ORIGINAL_DEFAULT_LAUNCHER";
+ private static final ComponentName LAUNCHER_ACTIVITY =
+ new ComponentName(
+ "com.android.cts.launchertests.support",
+ "com.android.cts.launchertests.support.LauncherActivity");
+
+ private static final ComponentName COMMAND_RECEIVER =
+ new ComponentName(
+ "com.android.cts.launchertests.support",
+ "com.android.cts.launchertests.support.QuietModeCommandReceiver");
+
+ private UserManager mUserManager;
+ private UserHandle mTargetUser;
+ private Context mContext;
+ private String mOriginalLauncher;
+ private UiDevice mUiDevice;
+
+ @Before
+ public void setupUserManager() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mUserManager = mContext.getSystemService(UserManager.class);
+ }
+
+ @Before
+ public void readParams() {
+ Context context = InstrumentationRegistry.getContext();
+ Bundle arguments = InstrumentationRegistry.getArguments();
+ UserManager userManager = context.getSystemService(UserManager.class);
+ final int userSn = Integer.parseInt(arguments.getString(PARAM_TARGET_USER));
+ mTargetUser = userManager.getUserForSerialNumber(userSn);
+ mOriginalLauncher = arguments.getString(PARAM_ORIGINAL_DEFAULT_LAUNCHER);
+ }
+
+ @Before
+ public void wakeupDeviceAndUnlock() throws Exception {
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mUiDevice.wakeUp();
+ mUiDevice.pressMenu();
+ }
+
+ @Before
+ @After
+ public void revertToDefaultLauncher() throws Exception {
+ if (TextUtils.isEmpty(mOriginalLauncher)) {
+ return;
+ }
+ setDefaultLauncher(InstrumentationRegistry.getInstrumentation(), mOriginalLauncher);
+ startActivitySync(mOriginalLauncher);
+ }
+
+ @Test
+ public void testTryEnableQuietMode_defaultForegroundLauncher() throws Exception {
+ setTestAppAsDefaultLauncher();
+ startLauncherActivityInTestApp();
+
+ Intent intent = trySetQuietModeEnabled(true);
+ assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_UNAVAILABLE broadcast", intent);
+ assertTrue(mUserManager.isQuietModeEnabled(mTargetUser));
+
+ intent = trySetQuietModeEnabled(false);
+ assertNotNull("Failed to receive ACTION_MANAGED_PROFILE_AVAILABLE broadcast", intent);
+ assertFalse(mUserManager.isQuietModeEnabled(mTargetUser));
+ }
+
+ @Test
+ public void testTryEnableQuietMode_notForegroundLauncher() throws InterruptedException {
+ setTestAppAsDefaultLauncher();
+
+ assertThrows(SecurityException.class, () -> trySetQuietModeEnabled(true));
+ assertFalse(mUserManager.isQuietModeEnabled(mTargetUser));
+ }
+
+ @Test
+ public void testTryEnableQuietMode_notDefaultLauncher() throws Exception {
+ startLauncherActivityInTestApp();
+
+ assertThrows(SecurityException.class, () -> trySetQuietModeEnabled(true));
+ assertFalse(mUserManager.isQuietModeEnabled(mTargetUser));
+ }
+
+ private Intent trySetQuietModeEnabled(boolean enabled) throws Exception {
+ final String action = enabled
+ ? Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE
+ : Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
+
+ BlockingBroadcastReceiver receiver =
+ new BlockingBroadcastReceiver(mContext, action);
+ try {
+ receiver.register();
+
+ boolean notShowingConfirmCredential = askLauncherSupportAppToSetQuietMode(enabled);
+ assertTrue(notShowingConfirmCredential);
+
+ return receiver.awaitForBroadcast();
+ } finally {
+ receiver.unregisterQuietly();
+ }
+ }
+
+ /**
+ * Ask launcher support test app to set quiet mode by sending broadcast.
+ * <p>
+ * We cannot simply make this package the launcher and call the API because instrumentation
+ * process would always considered to be in the foreground. The trick here is to send
+ * broadcast to another test app which is launcher itself and call the API through it.
+ * The receiver will then send back the result, and it should be either true, false or
+ * security-exception.
+ * <p>
+ * All the constants defined here should be aligned with
+ * com.android.cts.launchertests.support.QuietModeCommandReceiver.
+ */
+ private boolean askLauncherSupportAppToSetQuietMode(boolean enabled) throws Exception {
+ Intent intent = new Intent("toggle_quiet_mode");
+ intent.setComponent(COMMAND_RECEIVER);
+ intent.putExtra("quiet_mode", enabled);
+ intent.putExtra(Intent.EXTRA_USER, mTargetUser);
+
+ // Ask launcher support app to set quiet mode by sending broadcast.
+ LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
+ mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ blockingQueue.offer(getResultData());
+ }
+ }, null, 0, "", null);
+
+ // Wait for the result.
+ String result = null;
+ for (int i = 0; i < 10; i++) {
+ // Broadcast won't be delivered when the device is sleeping, so wake up the device
+ // in between each attempt.
+ wakeupDeviceAndUnlock();
+ result = blockingQueue.poll(10, TimeUnit.SECONDS);
+ if (!TextUtils.isEmpty(result)) {
+ break;
+ }
+ }
+
+ // Parse the result.
+ assertNotNull(result);
+ if ("true".equalsIgnoreCase(result)) {
+ return true;
+ } else if ("false".equalsIgnoreCase(result)) {
+ return false;
+ } else if ("security-exception".equals(result)) {
+ throw new SecurityException();
+ }
+ throw new IllegalStateException("Unexpected result : " + result);
+ }
+
+ private void startActivitySync(String activity) throws Exception {
+ mUiDevice.executeShellCommand("am start -W -n " + activity);
+ }
+
+ /**
+ * Start the launcher activity in the test app to make it foreground.
+ */
+ private void startLauncherActivityInTestApp() throws Exception {
+ startActivitySync(LAUNCHER_ACTIVITY.flattenToString());
+ }
+
+ private void setTestAppAsDefaultLauncher() {
+ setDefaultLauncher(
+ InstrumentationRegistry.getInstrumentation(),
+ LAUNCHER_ACTIVITY.flattenToString());
+ }
+}
+
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
index dc71264..14abd1a 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -26,5 +26,19 @@
<action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
</intent-filter>
</service>
+
+ <activity android:name=".LauncherActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.HOME"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <receiver android:name=".QuietModeCommandReceiver" android:exported="true">
+ <intent-filter>
+ <action android:name="toggle_quiet_mode"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherActivity.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherActivity.java
new file mode 100644
index 0000000..348e46c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.launchertests.support;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/QuietModeCommandReceiver.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/QuietModeCommandReceiver.java
new file mode 100644
index 0000000..460e629
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/QuietModeCommandReceiver.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.launchertests.support;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+/**
+ * Runs {@link UserManager#trySetQuietModeEnabled(boolean, UserHandle)} APIs by receiving
+ * broadcast and returns the result back to the broadcast sender.
+ */
+public class QuietModeCommandReceiver extends BroadcastReceiver {
+ private static final String TAG = "QuietModeReceiver";
+ private static final String ACTION_TOGGLE_QUIET_MODE = "toggle_quiet_mode";
+ private static final String EXTRA_QUIET_MODE = "quiet_mode";
+ private static final String RESULT_SECURITY_EXCEPTION = "security-exception";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!ACTION_TOGGLE_QUIET_MODE.equals(intent.getAction())) {
+ return;
+ }
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final boolean enableQuietMode = intent.getBooleanExtra(EXTRA_QUIET_MODE, false);
+ final UserHandle targetUser = intent.getParcelableExtra(Intent.EXTRA_USER);
+ String result;
+ try {
+ final boolean setQuietModeResult =
+ userManager.trySetQuietModeEnabled(enableQuietMode, targetUser);
+ result = Boolean.toString(setQuietModeResult);
+ Log.i(TAG, "trySetQuietModeEnabled returns " + setQuietModeResult);
+ } catch (SecurityException ex) {
+ Log.i(TAG, "trySetQuietModeEnabled throws security exception", ex);
+ result = RESULT_SECURITY_EXCEPTION;
+ }
+ setResultData(result);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index b947a61..3e81296 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES = \
android-support-v4 \
@@ -32,8 +32,7 @@
compatibility-device-util \
ub-uiautomator \
android-support-test \
- guava \
- legacy-android-test
+ guava
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 05ebac0..26db11b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -145,6 +145,11 @@
</intent-filter>
</activity>
+ <activity android:name=".WebViewActivity"
+ android:process=":testProcess"/>
+
+ <activity android:name=".TimeoutActivity" android:exported="true"/>
+
<service
android:name=".CrossProfileNotificationListenerService"
android:label="CrossProfileNotificationListenerService"
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
index 905f7d5..f10822c 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
@@ -19,7 +19,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
-import android.support.test.uiautomator.UiDevice;
import android.test.InstrumentationTestCase;
/**
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java
index 550f1d3..4e2f58f 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CurrentApiHelper.java
@@ -168,6 +168,9 @@
typeName = typeName.substring(0, typeName.length() - 2);
}
+ // Resolve inner classes
+ typeName = typeName.replaceAll("([A-Z].*)\\.", "$1\\$");
+
// Remove type parameters, if any
typeName = typeName.replaceAll("<.*>$", "");
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/IsUsingUnifiedPasswordTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/IsUsingUnifiedPasswordTest.java
new file mode 100644
index 0000000..971f144
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/IsUsingUnifiedPasswordTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.managedprofile;
+
+/**
+ * Test to check reporting the state of profile challenge.
+ */
+public class IsUsingUnifiedPasswordTest extends BaseManagedProfileTest {
+
+ public void testNotUsingUnifiedPassword() {
+ assertFalse(mDevicePolicyManager.isUsingUnifiedPassword(ADMIN_RECEIVER_COMPONENT));
+ }
+
+ public void testUsingUnifiedPassword() {
+ assertTrue(mDevicePolicyManager.isUsingUnifiedPassword(ADMIN_RECEIVER_COMPONENT));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java
index fa74db7..fd5fcd2 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/LockNowTest.java
@@ -18,8 +18,6 @@
import android.app.admin.DevicePolicyManager;
-import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
-
/**
* Test lockNow() for use in a managed profile. If called from a managed profile. lockNow() can be
* passed a flag to evict the CE key of the profile.
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java
new file mode 100644
index 0000000..a386baa
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProfileTimeoutTestHelper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.managedprofile;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
+
+/**
+ * Helper to set lock timeouts and check if the profile is locked.
+ */
+public class ProfileTimeoutTestHelper extends InstrumentationTestCase {
+ // This should be sufficiently smaller than ManagedProfileTest.PROFILE_TIMEOUT_DELAY_SEC.
+ private static final int TIMEOUT_MS = 5_000;
+ private static final ComponentName ADMIN_COMPONENT = new ComponentName(
+ BasicAdminReceiver.class.getPackage().getName(), BasicAdminReceiver.class.getName());
+
+ private KeyguardManager mKm;
+ private DevicePolicyManager mDpm;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getContext();
+ mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mKm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+ }
+
+ public void testSetWorkProfileTimeout() {
+ assertProfileOwner();
+ mDpm.setMaximumTimeToLock(ADMIN_COMPONENT, TIMEOUT_MS);
+ assertEquals("Failed to set timeout",
+ TIMEOUT_MS, mDpm.getMaximumTimeToLock(ADMIN_COMPONENT));
+ }
+
+ public void testDeviceLocked() {
+ assertTrue("Device not locked", mKm.isDeviceLocked());
+ }
+
+ public void testDeviceNotLocked() {
+ assertFalse("Device locked", mKm.isDeviceLocked());
+ }
+
+ private void assertProfileOwner() {
+ assertTrue(mDpm.isProfileOwnerApp(ADMIN_COMPONENT.getPackageName()));
+ assertTrue(mDpm.isManagedProfile(ADMIN_COMPONENT));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/TimeoutActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/TimeoutActivity.java
new file mode 100644
index 0000000..a574477
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/TimeoutActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.managedprofile;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Activity to test profile lock timeout.
+ */
+public class TimeoutActivity extends Activity {
+ private static final String KEEP_SCREEN_ON = "keep_screen_on";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final View blankView = new View(this);
+ setContentView(blankView);
+
+ if (getIntent().getBooleanExtra(KEEP_SCREEN_ON, false)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+}
+
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WebViewActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WebViewActivity.java
new file mode 100644
index 0000000..b5e16f4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WebViewActivity.java
@@ -0,0 +1,14 @@
+package com.android.cts.managedprofile;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+public class WebViewActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(new WebView(this));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index 7ef3a0a..26f627e 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -15,16 +15,8 @@
*/
package com.android.cts.managedprofile;
-
-import com.android.cts.managedprofile.BaseManagedProfileTest.BasicAdminReceiver;
-
-import org.junit.Ignore;
-
-/**
- * Test wipeData() for use in managed profile. If called from a managed profile, wipeData() should
- * remove the current managed profile. Also, no erasing of external storage should be allowed.
- */
public class WipeDataTest extends BaseManagedProfileTest {
+ private static final String TEST_WIPE_DATA_REASON = "cts test for WipeDataWithReason";
@Override
protected void setUp() throws Exception {
@@ -35,8 +27,25 @@
assertTrue(mDevicePolicyManager.isProfileOwnerApp(ADMIN_RECEIVER_COMPONENT.getPackageName()));
}
+ /**
+ * Test wipeData() for use in managed profile. If called from a managed profile, wipeData()
+ * should remove the current managed profile. Also, no erasing of external storage should be
+ * allowed.
+ */
public void testWipeData() throws InterruptedException {
mDevicePolicyManager.wipeData(0);
- // the test that the profile will indeed be removed is done in the host.
+ // The test that the profile will indeed be removed is done in the host.
+ }
+
+ /**
+ * Test wipeDataWithReason() for use in managed profile. If called from a managed profile,
+ * wipeDataWithReason() should remove the current managed profile.In the mean time, it should
+ * send out a notification containing the reason for wiping data to user. Also, no erasing of
+ * external storage should be allowed.
+ */
+ public void testWipeDataWithReason() throws InterruptedException {
+ mDevicePolicyManager.wipeDataWithReason(0, TEST_WIPE_DATA_REASON);
+ // The test that the profile will indeed be removed is done in the host.
+ // Notification verification is done in another test.
}
}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java
new file mode 100644
index 0000000..3b35cb4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataWithReasonVerificationTest.java
@@ -0,0 +1,37 @@
+package com.android.cts.managedprofile;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.test.AndroidTestCase;
+
+/**
+ * Test wipeDataWithReason() has indeed shown the notification.
+ * The function wipeDataWithReason() is called and executed in another test.
+ */
+public class WipeDataWithReasonVerificationTest extends AndroidTestCase {
+
+ private static final String WIPE_DATA_TITLE = "Work profile deleted";
+ // This reason string should be aligned with the one in WipeDataTest as this is a followup test.
+ private static final String TEST_WIPE_DATA_REASON = "cts test for WipeDataWithReason";
+ private static final long UI_TIMEOUT_MILLI = 5000;
+
+ public void testWipeDataWithReasonVerification() throws UiObjectNotFoundException,
+ InterruptedException {
+ // The function wipeDataWithReason() is called and executed in another test.
+ // The data should be wiped and notification should be sent before.
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ uiDevice.openNotification();
+ Boolean wipeDataTitleExist = uiDevice.wait(Until.hasObject(By.text(WIPE_DATA_TITLE)),
+ UI_TIMEOUT_MILLI);
+ Boolean wipeDataReasonExist = uiDevice.wait(Until.hasObject(By.text(TEST_WIPE_DATA_REASON)),
+ UI_TIMEOUT_MILLI);
+
+ // Verify the notification is there.
+ assertEquals("Wipe notification title not found", Boolean.TRUE, wipeDataTitleExist);
+ assertEquals("Wipe notification content not found",
+ Boolean.TRUE, wipeDataReasonExist);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk b/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
index 11680e9..a52ed2a 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/Android.mk
@@ -24,13 +24,12 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v4 \
ctstestrunner \
- ub-uiautomator \
- legacy-android-test
+ ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
index 34b8e08..e19b119 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
@@ -24,13 +24,17 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ conscrypt \
+ cts-junit \
+ android.test.base.stubs \
+
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
compatibility-device-util \
- ub-uiautomator \
- legacy-android-test
+ ub-uiautomator
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk
index 426dbf2..4e5d874 100644
--- a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk
+++ b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.mk
@@ -31,8 +31,7 @@
ctstestrunner \
compatibility-device-util \
ub-uiautomator \
- android-support-test \
- legacy-android-test
+ android-support-test
LOCAL_SDK_VERSION := test_current
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
new file mode 100644
index 0000000..31e2dc4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsTransferOwnerIncomingApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ ctstestrunner \
+ compatibility-device-util \
+ ub-uiautomator \
+ android-support-test \
+ testng
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
new file mode 100644
index 0000000..2a64ca2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.transferownerincoming">
+
+ <uses-sdk android:minSdkVersion="24"/>
+
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+ <application
+ android:testOnly="true">
+
+ <uses-library android:name="android.test.runner"/>
+ <receiver
+ android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin"/>
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.transferownerincoming"
+ android:label="Transfer Owner CTS tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..66a3730
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/res/xml/device_admin.xml
@@ -0,0 +1,12 @@
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+ <uses-policies>
+ <limit-password/>
+ <watch-login/>
+ <reset-password/>
+ <force-lock/>
+ <wipe-data/>
+ <expire-password/>
+ <encrypted-storage/>
+ <disable-camera/>
+ </uses-policies>
+</device-admin>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
new file mode 100644
index 0000000..0d22c38
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+
+@SmallTest
+public class DeviceAndProfileOwnerTransferIncomingTest {
+ public static class BasicAdminReceiver extends DeviceAdminReceiver {
+ public BasicAdminReceiver() {}
+ }
+
+ protected ComponentName mIncomingComponentName;
+ protected DevicePolicyManager mDevicePolicyManager;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mIncomingComponentName = new ComponentName(context, BasicAdminReceiver.class.getName());
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
new file mode 100644
index 0000000..2b20c59
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.SystemUpdatePolicy;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class TransferDeviceOwnerIncomingTest extends DeviceAndProfileOwnerTransferIncomingTest {
+ @Test
+ public void testTransferPoliciesAreRetainedAfterTransfer() {
+ assertTrue(mDevicePolicyManager.isAdminActive(mIncomingComponentName));
+ assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mIncomingComponentName.getPackageName()));
+ assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
+ assertEquals(Collections.singletonList("test.package"),
+ mDevicePolicyManager.getKeepUninstalledPackages(mIncomingComponentName));
+ assertEquals(123, mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
+ assertTrue(areSystemPoliciesEqual(SystemUpdatePolicy.createWindowedInstallPolicy(123, 456),
+ mDevicePolicyManager.getSystemUpdatePolicy()));
+ assertThrows(SecurityException.class, () -> {
+ mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
+ });
+ }
+
+ private boolean areSystemPoliciesEqual(SystemUpdatePolicy policy1, SystemUpdatePolicy policy2) {
+ return policy1.getPolicyType() == policy2.getPolicyType()
+ && policy1.getInstallWindowStart() == policy2.getInstallWindowStart()
+ && policy1.getInstallWindowEnd() == policy2.getInstallWindowEnd();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
new file mode 100644
index 0000000..ef7e8ac
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.admin.DevicePolicyManager;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TransferProfileOwnerIncomingTest extends DeviceAndProfileOwnerTransferIncomingTest {
+ @Test
+ public void testTransferPoliciesAreRetainedAfterTransfer() {
+ int passwordLength = 123;
+ int passwordExpirationTimeout = 456;
+ assertTrue(mDevicePolicyManager.isAdminActive(mIncomingComponentName));
+ assertTrue(mDevicePolicyManager.isProfileOwnerApp(mIncomingComponentName.getPackageName()));
+ assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
+ assertTrue(mDevicePolicyManager.getCrossProfileCallerIdDisabled(mIncomingComponentName));
+ assertEquals(
+ passwordLength,
+ mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
+
+ DevicePolicyManager targetParentProfileInstance =
+ mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
+ assertEquals(
+ passwordExpirationTimeout,
+ targetParentProfileInstance.getPasswordExpirationTimeout(mIncomingComponentName));
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
new file mode 100644
index 0000000..6f6297d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsTransferOwnerOutgoingApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ ctstestrunner \
+ compatibility-device-util \
+ ub-uiautomator \
+ android-support-test \
+ testng
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
new file mode 100644
index 0000000..59feeb3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.transferowneroutgoing">
+
+ <uses-sdk android:minSdkVersion="24"/>
+
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+ <application
+ android:testOnly="true">
+
+ <uses-library android:name="android.test.runner"/>
+ <receiver
+ android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin"/>
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.transferowneroutgoing"
+ android:label="Transfer Owner CTS tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..66a3730
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/res/xml/device_admin.xml
@@ -0,0 +1,12 @@
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+ <uses-policies>
+ <limit-password/>
+ <watch-login/>
+ <reset-password/>
+ <force-lock/>
+ <wipe-data/>
+ <expire-password/>
+ <encrypted-storage/>
+ <disable-camera/>
+ </uses-policies>
+</device-admin>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
new file mode 100644
index 0000000..888ca49
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.InvocationTargetException;
+
+public abstract class DeviceAndProfileOwnerTransferOutgoingTest {
+ public static class BasicAdminReceiver extends DeviceAdminReceiver {
+ public BasicAdminReceiver() {}
+ }
+
+ private static final String TRANSFER_OWNER_INCOMING_PKG =
+ "com.android.cts.transferownerincoming";
+ private static final String TRANSFER_OWNER_INCOMING_TEST_RECEIVER_CLASS =
+ "com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver";
+ static final ComponentName mIncomingComponentName =
+ new ComponentName(
+ TRANSFER_OWNER_INCOMING_PKG, TRANSFER_OWNER_INCOMING_TEST_RECEIVER_CLASS);
+ private static final ComponentName mInvalidTargetComponent =
+ new ComponentName("com.android.cts.intent.receiver", ".BroadcastIntentReceiver");
+
+ protected DevicePolicyManager mDevicePolicyManager;
+ protected ComponentName mOutgoingComponentName;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mOutgoingComponentName = new ComponentName(context, BasicAdminReceiver.class.getName());
+ }
+
+ @Test
+ public void testTransferSameAdmin() {
+ PersistableBundle b = new PersistableBundle();
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ transferOwner(
+ mOutgoingComponentName, mOutgoingComponentName, b);
+ });
+ }
+
+ @Test
+ public void testTransferInvalidTarget() {
+ PersistableBundle b = new PersistableBundle();
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ transferOwner(
+ mOutgoingComponentName, mInvalidTargetComponent, b);
+ });
+ }
+
+ protected void transferOwner(ComponentName outgoing, ComponentName incoming,
+ PersistableBundle parameters)
+ throws Throwable {
+ try {
+ mDevicePolicyManager.getClass().getMethod("transferOwner",
+ ComponentName.class, ComponentName.class, PersistableBundle.class)
+ .invoke(mDevicePolicyManager, outgoing, incoming, parameters);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
new file mode 100644
index 0000000..67c3760
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.SystemUpdatePolicy;
+import android.os.PersistableBundle;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class TransferDeviceOwnerOutgoingTest extends DeviceAndProfileOwnerTransferOutgoingTest {
+ @Test
+ public void testTransferWithPoliciesOutgoing() throws Throwable {
+ int passwordLength = 123;
+ mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+ mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
+ mDevicePolicyManager.setKeepUninstalledPackages(mOutgoingComponentName,
+ Collections.singletonList("test.package"));
+ mDevicePolicyManager.setSystemUpdatePolicy(mOutgoingComponentName,
+ SystemUpdatePolicy.createWindowedInstallPolicy(123, 456));
+
+ PersistableBundle b = new PersistableBundle();
+ transferOwner(mOutgoingComponentName, mIncomingComponentName, b);
+ }
+
+ @Test
+ public void testTransfer() throws Throwable {
+ PersistableBundle b = new PersistableBundle();
+ transferOwner(mOutgoingComponentName, mIncomingComponentName, b);
+ assertTrue(mDevicePolicyManager.isAdminActive(mIncomingComponentName));
+ assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mIncomingComponentName.getPackageName()));
+ assertFalse(
+ mDevicePolicyManager.isDeviceOwnerApp(mOutgoingComponentName.getPackageName()));
+ assertFalse(mDevicePolicyManager.isAdminActive(mOutgoingComponentName));
+ assertThrows(SecurityException.class, () -> {
+ mDevicePolicyManager.getSecondaryUsers(mOutgoingComponentName);
+ });
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
new file mode 100644
index 0000000..370539d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.transferowner;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.PersistableBundle;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TransferProfileOwnerOutgoingTest extends DeviceAndProfileOwnerTransferOutgoingTest {
+ @Test
+ public void testTransferWithPoliciesOutgoing() throws Throwable {
+ int passwordLength = 123;
+ int passwordExpirationTimeout = 456;
+ DevicePolicyManager parentDevicePolicyManager =
+ mDevicePolicyManager.getParentProfileInstance(mOutgoingComponentName);
+ mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+ mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
+ mDevicePolicyManager.setCrossProfileCallerIdDisabled(mOutgoingComponentName, true);
+ parentDevicePolicyManager.setPasswordExpirationTimeout(
+ mOutgoingComponentName, passwordExpirationTimeout);
+
+ PersistableBundle b = new PersistableBundle();
+ transferOwner(mOutgoingComponentName, mIncomingComponentName, b);
+ }
+
+ @Test
+ public void testTransfer() throws Throwable {
+ PersistableBundle b = new PersistableBundle();
+ transferOwner(mOutgoingComponentName, mIncomingComponentName, b);
+ assertTrue(mDevicePolicyManager.isAdminActive(mIncomingComponentName));
+ assertTrue(mDevicePolicyManager.isProfileOwnerApp(mIncomingComponentName.getPackageName()));
+ assertFalse(
+ mDevicePolicyManager.isProfileOwnerApp(mOutgoingComponentName.getPackageName()));
+ assertFalse(mDevicePolicyManager.isAdminActive(mOutgoingComponentName));
+ assertThrows(SecurityException.class, () -> {
+ mDevicePolicyManager.setCrossProfileCallerIdDisabled(mOutgoingComponentName,
+ false);
+ });
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml b/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
index 1b98259..cd3f9b7 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name=".WifiConfigCreatorActivity"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay"
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index a612cee..f88c380 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -27,10 +27,16 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.TarUtil;
+import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -229,16 +235,21 @@
return 0;
}
+ private void stopUserAsync(int userId) throws Exception {
+ String stopUserCommand = "am stop-user -f " + userId;
+ CLog.d("starting command \"" + stopUserCommand);
+ CLog.d("Output for command " + stopUserCommand + ": "
+ + getDevice().executeShellCommand(stopUserCommand));
+ }
+
protected void stopUser(int userId) throws Exception {
- // Wait for the broadcast queue to be idle first to workaround the stop-user timeout issue.
- waitForBroadcastIdle();
String stopUserCommand = "am stop-user -w -f " + userId;
CLog.d("starting command \"" + stopUserCommand + "\" and waiting.");
CLog.d("Output for command " + stopUserCommand + ": "
+ getDevice().executeShellCommand(stopUserCommand));
}
- private void waitForBroadcastIdle() throws DeviceNotAvailableException {
+ protected void waitForBroadcastIdle() throws DeviceNotAvailableException, IOException {
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
try {
// we allow 8min for the command to complete and 4min for the command to start to
@@ -249,6 +260,24 @@
String output = receiver.getOutput();
CLog.d("Output from 'am wait-for-broadcast-idle': %s", output);
if (!output.contains("All broadcast queues are idle!")) {
+ // Gather the system_server dump data for investigation.
+ File heapDump = getDevice().dumpHeap("system_server", "/data/local/tmp/dump.hprof");
+ if (heapDump != null) {
+ // If file is too too big, tar if with TarUtil.
+ String pid = getDevice().getProcessPid("system_server");
+ // gzip the file it's quite big
+ File heapDumpGz = TarUtil.gzip(heapDump);
+ try (FileInputStreamSource source = new FileInputStreamSource(heapDumpGz)) {
+ addTestLog(
+ String.format("system_server_dump.%s.%s.hprof",
+ pid, getDevice().getDeviceDate()),
+ LogDataType.GZIP, source);
+ } finally {
+ FileUtil.deleteFile(heapDump);
+ }
+ } else {
+ CLog.e("Failed to capture the dumpheap from system_server");
+ }
// the call most likely failed we should fail the test
fail("'am wait-for-broadcase-idle' did not complete.");
// TODO: consider adding a reboot or recovery before failing if necessary
@@ -268,7 +297,16 @@
}
protected void removeTestUsers() throws Exception {
- for (int userId : getUsersCreatedByTests()) {
+ List<Integer> usersCreatedByTests = getUsersCreatedByTests();
+
+ // The time spent on stopUser is depend on how busy the broadcast queue is.
+ // To optimize the time to remove multiple test users, we mark all users as
+ // stopping first, so no more broadcasts will be sent to these users, which make the queue
+ // less busy.
+ for (int userId : usersCreatedByTests) {
+ stopUserAsync(userId);
+ }
+ for (int userId : usersCreatedByTests) {
removeUser(userId);
}
}
@@ -807,4 +845,28 @@
executeShellCommand("input keyevent KEYCODE_WAKEUP");
executeShellCommand("wm dismiss-keyguard");
}
+
+ protected void startActivityAsUser(int userId, String packageName, String activityName)
+ throws Exception {
+ wakeupAndDismissKeyguard();
+ String command = "am start -W --user " + userId + " " + packageName + "/" + activityName;
+ getDevice().executeShellCommand(command);
+ }
+
+ protected String getDefaultLauncher() throws Exception {
+ final String PREFIX = "Launcher: ComponentInfo{";
+ final String POSTFIX = "}";
+ final String commandOutput =
+ getDevice().executeShellCommand("cmd shortcut get-default-launcher");
+ if (commandOutput == null) {
+ return null;
+ }
+ String[] lines = commandOutput.split("\\r?\\n");
+ for (String line : lines) {
+ if (line.startsWith(PREFIX) && line.endsWith(POSTFIX)) {
+ return line.substring(PREFIX.length(), line.length() - POSTFIX.length());
+ }
+ }
+ throw new Exception("Default launcher not found");
+ }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
new file mode 100644
index 0000000..b1cf3ff
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -0,0 +1,99 @@
+package com.android.cts.devicepolicy;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * In the test, managed profile and secondary user are created. We then verify
+ * {@link android.content.pm.crossprofile.CrossProfileApps} APIs in different directions, like
+ * primary user to managed profile.
+ */
+public class CrossProfileAppsHostSideTest extends BaseDevicePolicyTest {
+ private static final String TEST_PACKAGE = "com.android.cts.crossprofileappstest";
+ private static final String NON_TARGET_USER_TEST_CLASS = ".CrossProfileAppsNonTargetUserTest";
+ private static final String TARGET_USER_TEST_CLASS = ".CrossProfileAppsTargetUserTest";
+ private static final String PARAM_TARGET_USER = "TARGET_USER";
+ private static final String EXTRA_TEST_APK = "CtsCrossProfileAppsTests.apk";
+
+ private int mProfileId;
+ private int mSecondaryUserId;
+ private boolean mHasManagedUserFeature;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // We need managed users to be supported in order to create a profile of the user owner.
+ mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
+ installAppAsUser(EXTRA_TEST_APK, mPrimaryUserId);
+
+ if (mHasManagedUserFeature) {
+ createAndStartManagedProfile();
+ installAppAsUser(EXTRA_TEST_APK, mProfileId);
+ }
+ if (mSupportsMultiUser) {
+ mSecondaryUserId = createUser();
+ installAppAsUser(EXTRA_TEST_APK, mSecondaryUserId);
+ }
+ }
+
+ public void testPrimaryUserToPrimaryUser() throws Exception {
+ verifyCrossProfileAppsApi(mPrimaryUserId, mPrimaryUserId, NON_TARGET_USER_TEST_CLASS);
+ }
+
+ public void testPrimaryUserToManagedProfile() throws Exception {
+ if (!mHasManagedUserFeature) {
+ return;
+ }
+ verifyCrossProfileAppsApi(mPrimaryUserId, mProfileId, TARGET_USER_TEST_CLASS);
+ }
+
+ public void testManagedProfileToPrimaryUser() throws Exception {
+ if (!mHasManagedUserFeature) {
+ return;
+ }
+ verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, TARGET_USER_TEST_CLASS);
+ }
+
+ public void testPrimaryUserToSecondaryUser() throws Exception {
+ if (!mSupportsMultiUser) {
+ return;
+ }
+ verifyCrossProfileAppsApi(mPrimaryUserId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
+ }
+
+ public void testSecondaryUserToManagedProfile() throws Exception {
+ if (!mSupportsMultiUser || !mHasManagedUserFeature) {
+ return;
+ }
+ verifyCrossProfileAppsApi(mSecondaryUserId, mProfileId, NON_TARGET_USER_TEST_CLASS);
+
+ }
+
+ public void testManagedProfileToSecondaryUser() throws Exception {
+ if (!mSupportsMultiUser || !mHasManagedUserFeature) {
+ return;
+ }
+ verifyCrossProfileAppsApi(mProfileId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
+ }
+
+ private void verifyCrossProfileAppsApi(int fromUserId, int targetUserId, String testClass)
+ throws Exception {
+ runDeviceTestsAsUser(
+ TEST_PACKAGE,
+ testClass,
+ null,
+ fromUserId,
+ createTargetUserParam(targetUserId));
+ }
+
+ private void createAndStartManagedProfile() throws Exception {
+ mProfileId = createManagedProfile(mPrimaryUserId);
+ switchUser(mPrimaryUserId);
+ startUser(mProfileId);
+ }
+
+ private Map<String, String> createTargetUserParam(int targetUserId) throws Exception {
+ return Collections.singletonMap(PARAM_TARGET_USER,
+ Integer.toString(getUserSerialNumber(targetUserId)));
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index 4042fd5..7b568af 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -31,7 +31,7 @@
private static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
private static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
private static final String DEVICE_OWNER_ADMIN
- = DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
+ = DEVICE_OWNER_PKG + ".BasicAdminReceiver";
private static final String DEVICE_OWNER_ADMIN_COMPONENT
= DEVICE_OWNER_PKG + "/" + DEVICE_OWNER_ADMIN;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 6b7d89b..3b85093 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -253,6 +253,15 @@
executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
}
+ public void testPermissionGrant_developmentPermission() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppPermissionAppAsUser();
+ executeDeviceTestMethod(
+ ".PermissionsTest", "testPermissionGrantState_developmentPermission");
+ }
+
/**
* Require a device for tests that use the network stack. Headless Androids running in
* data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
@@ -774,6 +783,13 @@
}
+ public void testPasswordBlacklist() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ executeDeviceTestClass(".PasswordBlacklistTest");
+ }
+
public void testRequiredStrongAuthTimeout() throws Exception {
if (!mHasFeature) {
return;
@@ -814,6 +830,18 @@
Collections.singletonMap(ARG_ALLOW_FAILURE, Boolean.toString(allowFailures)));
}
+ public void testClearApplicationData() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(INTENT_RECEIVER_APK, mUserId);
+ runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
+ "testWriteToSharedPreference", mUserId);
+ executeDeviceTestMethod(".ClearApplicationDataTest", "testClearApplicationData");
+ runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
+ "testSharedPreferenceCleared", mUserId);
+ }
+
protected void executeDeviceTestClass(String className) throws Exception {
runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
}
@@ -904,10 +932,7 @@
*/
protected void startSimpleActivityAsUser(int userId) throws Exception {
installAppAsUser(TEST_APP_APK, userId);
- wakeupAndDismissKeyguard();
- String command = "am start -W --user " + userId + " " + TEST_APP_PKG + "/"
- + TEST_APP_PKG + ".SimpleActivity";
- getDevice().executeShellCommand(command);
+ startActivityAsUser(userId, TEST_APP_PKG, TEST_APP_PKG + ".SimpleActivity");
}
protected void setScreenCaptureDisabled(int userId, boolean disabled) throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTransferTest.java
new file mode 100644
index 0000000..b9cb16b
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTransferTest.java
@@ -0,0 +1,75 @@
+package com.android.cts.devicepolicy;
+
+public class DeviceAndProfileOwnerTransferTest extends BaseDevicePolicyTest {
+ protected static final String TRANSFER_OWNER_OUTGOING_PKG =
+ "com.android.cts.transferowneroutgoing";
+ protected static final String TRANSFER_OWNER_OUTGOING_APK = "CtsTransferOwnerOutgoingApp.apk";
+ protected static final String TRANSFER_OWNER_OUTGOING_TEST_RECEIVER =
+ TRANSFER_OWNER_OUTGOING_PKG
+ + "/com.android.cts.transferowner"
+ + ".DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver";
+ protected static final String TRANSFER_OWNER_INCOMING_PKG =
+ "com.android.cts.transferownerincoming";
+ protected static final String TRANSFER_OWNER_INCOMING_APK = "CtsTransferOwnerIncomingApp.apk";
+ protected static final String INVALID_TARGET_APK = "CtsIntentReceiverApp.apk";
+
+ protected int mUserId;
+ protected String mOutgoingTestClassName;
+ protected String mIncomingTestClassName;
+
+ public void testTransfer() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
+ runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+ mOutgoingTestClassName,
+ "testTransfer", mUserId);
+ }
+
+ public void testTransferSameAdmin() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
+ runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+ mOutgoingTestClassName,
+ "testTransferSameAdmin", mUserId);
+ }
+
+ public void testTransferInvalidTarget() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(INVALID_TARGET_APK, mUserId);
+ runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+ mOutgoingTestClassName,
+ "testTransferInvalidTarget", mUserId);
+ }
+
+ public void testTransferPolicies() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
+ runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
+ mOutgoingTestClassName,
+ "testTransferWithPoliciesOutgoing", mUserId);
+ runDeviceTestsAsUser(TRANSFER_OWNER_INCOMING_PKG,
+ mIncomingTestClassName,
+ "testTransferPoliciesAreRetainedAfterTransfer", mUserId);
+ }
+
+ protected void setupTestParameters(int userId, String outgoingTestClassName,
+ String incomingTestClassName) {
+ mUserId = userId;
+ mOutgoingTestClassName = outgoingTestClassName;
+ mIncomingTestClassName = incomingTestClassName;
+ }
+
+ /* TODO: Add tests for:
+ * 1. startServiceForOwner
+ * 2. passwordOwner
+ *
+ * */
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 7a07d7c..e37c494 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,7 +16,12 @@
package com.android.cts.devicepolicy;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+
+import java.io.File;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -42,18 +47,30 @@
private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
private static final String ADMIN_RECEIVER_TEST_CLASS =
- DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
+ DEVICE_OWNER_PKG + ".BasicAdminReceiver";
private static final String DEVICE_OWNER_COMPONENT = DEVICE_OWNER_PKG + "/"
+ ADMIN_RECEIVER_TEST_CLASS;
- /** The ephemeral users are implemented and supported on the device. */
- private boolean mHasEphemeralUserFeature;
+ private static final String TEST_APP_APK = "CtsEmptyTestApp.apk";
+ private static final String TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
+ private static final String TEST_APP_LOCATION = "/data/local/tmp/cts/packageinstaller/";
+
+ private static final String ARG_SECURITY_LOGGING_BATCH_NUMBER = "batchNumber";
+ private static final int BUFFER_SECURITY_ENTRIES_NOTIFICATION_LEVEL = 1024;
+
+ private static final String ARG_NETWORK_LOGGING_BATCH_COUNT = "batchCount";
+
+ /** Forcing ephemeral users is implemented and supported on the device. */
+ private boolean mHasForceEphemeralUserFeature;
/**
- * Ephemeral users are implemented, but unsupported on the device (because of missing split
- * system user).
+ * Force ephemeral users feature is implemented, but unsupported on the device (because of
+ * missing split system user).
*/
- private boolean mHasDisabledEphemeralUserFeature;
+ private boolean mHasDisabledForceEphemeralUserFeature;
+
+ /** CreateAndManageUser is available and an additional user can be created. */
+ private boolean mHasCreateAndManageUserFeature;
@Override
protected void setUp() throws Exception {
@@ -67,9 +84,11 @@
fail("Failed to set device owner");
}
}
- mHasEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1) && hasUserSplit();
- mHasDisabledEphemeralUserFeature =
- mHasFeature && canCreateAdditionalUsers(1) && !hasUserSplit();
+ mHasForceEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1)
+ && hasUserSplit();
+ mHasDisabledForceEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1)
+ && !hasUserSplit();
+ mHasCreateAndManageUserFeature = mHasFeature && canCreateAdditionalUsers(1);
}
@Override
@@ -125,7 +144,7 @@
/** Tries to toggle the force-ephemeral-users on and checks it was really set. */
public void testSetForceEphemeralUsers() throws Exception {
- if (!mHasEphemeralUserFeature) {
+ if (!mHasForceEphemeralUserFeature) {
return;
}
// Set force-ephemeral-users policy and verify it was set.
@@ -136,7 +155,7 @@
* Setting force-ephemeral-users policy to true without a split system user should fail.
*/
public void testSetForceEphemeralUsersFailsWithoutSplitSystemUser() throws Exception {
- if (mHasDisabledEphemeralUserFeature) {
+ if (mHasDisabledForceEphemeralUserFeature) {
executeDeviceTestMethod(".ForceEphemeralUsersTest", "testSetForceEphemeralUsersFails");
}
}
@@ -148,7 +167,7 @@
* <p>If the current user is the system user, the other users are removed straight away.
*/
public void testRemoveUsersOnSetForceEphemeralUsers() throws Exception {
- if (!mHasEphemeralUserFeature) {
+ if (!mHasForceEphemeralUserFeature) {
return;
}
@@ -171,7 +190,7 @@
* before all other users are removed.
*/
public void testRemoveUsersOnSetForceEphemeralUsersWithUserSwitch() throws Exception {
- if (!mHasEphemeralUserFeature) {
+ if (!mHasForceEphemeralUserFeature) {
return;
}
@@ -206,7 +225,7 @@
/** The users created after setting force-ephemeral-users policy to true must be ephemeral. */
public void testCreateUserAfterSetForceEphemeralUsers() throws Exception {
- if (!mHasEphemeralUserFeature) {
+ if (!mHasForceEphemeralUserFeature) {
return;
}
@@ -221,7 +240,7 @@
* Test creating an epehemeral user using the DevicePolicyManager's createAndManageUser method.
*/
public void testCreateAndManageEphemeralUser() throws Exception {
- if (!mHasEphemeralUserFeature) {
+ if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
}
@@ -236,50 +255,45 @@
assertEquals("Ephemeral flag must be set", FLAG_EPHEMERAL, flags & FLAG_EPHEMERAL);
}
- /**
- * Test that creating an epehemeral user using the DevicePolicyManager's createAndManageUser
- * method fails on systems without the split system user.
- */
- public void testCreateAndManageEphemeralUserFailsWithoutSplitSystemUser() throws Exception {
- if (mHasDisabledEphemeralUserFeature) {
- executeDeviceTestMethod(
- ".CreateAndManageUserTest", "testCreateAndManageEphemeralUserFails");
+ public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
+ if (mHasCreateAndManageUserFeature) {
+ executeDeviceTestMethod(".CreateAndManageUserTest",
+ "testCreateAndManageUser_SkipSetupWizard");
}
}
-// Disabled due to b/29072728
-// public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
-// if (mHasCreateAndManageUserFeature) {
-// executeDeviceTestMethod(".CreateAndManageUserTest",
-// "testCreateAndManageUser_SkipSetupWizard");
-// }
-// }
-//
-// public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
-// if (mHasCreateAndManageUserFeature) {
-// executeDeviceTestMethod(".CreateAndManageUserTest",
-// "testCreateAndManageUser_DontSkipSetupWizard");
-// }
-// }
+ public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
+ if (mHasCreateAndManageUserFeature) {
+ executeDeviceTestMethod(".CreateAndManageUserTest",
+ "testCreateAndManageUser_DontSkipSetupWizard");
+ }
+ }
public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
if (mHasFeature && canCreateAdditionalUsers(1)) {
executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_AddRestrictionSet");
+ "testCreateAndManageUser_AddRestrictionSet");
}
}
public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
if (mHasFeature && canCreateAdditionalUsers(1)) {
executeDeviceTestMethod(".CreateAndManageUserTest",
- "testCreateAndManageUser_RemoveRestrictionSet");
+ "testCreateAndManageUser_RemoveRestrictionSet");
+ }
+ }
+
+ public void testCreateAndManageUser_StartUserInBackground() throws Exception {
+ if (mHasFeature && canCreateAdditionalUsers(1)) {
+ executeDeviceTestMethod(".CreateAndManageUserTest",
+ "testCreateAndManageUser_StartUserInBackground");
}
}
public void testUserAddedOrRemovedBroadcasts() throws Exception {
if (mHasFeature && canCreateAdditionalUsers(1)) {
executeDeviceTestMethod(".CreateAndManageUserTest",
- "testUserAddedOrRemovedBroadcasts");
+ "testUserAddedOrRemovedBroadcasts");
}
}
@@ -307,22 +321,53 @@
if (!mHasFeature) {
return;
}
- executeDeviceTestMethod(".SecurityLoggingTest",
- "testRetrievingSecurityLogsNotPossibleImmediatelyAfterPreviousSuccessfulRetrieval");
+ executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
+
+ // Generate more than enough events for a batch of security events.
+ int batchSize = BUFFER_SECURITY_ENTRIES_NOTIFICATION_LEVEL + 100;
try {
executeDeviceTestMethod(".SecurityLoggingTest", "testEnablingSecurityLogging");
+ // Reboot to ensure ro.device_owner is set to true in logd.
rebootAndWaitUntilReady();
- // Sleep for ~1 minute so that SecurityLogMonitor fetches the security events. This is
- // an implementation detail we shouldn't rely on but there is no other way to do it
- // currently.
- Thread.sleep(TimeUnit.SECONDS.toMillis(70));
- executeDeviceTestMethod(".SecurityLoggingTest", "testGetSecurityLogs");
+
+ // First batch: retrieve and verify the events.
+ runBatch(0, batchSize);
+
+ // Verify event ids are consistent across a consecutive batch.
+ runBatch(1, batchSize);
+
+ // Reboot the device, so the security event ids are reset.
+ rebootAndWaitUntilReady();
+
+ // First batch after reboot: retrieve and verify the events.
+ runBatch(0 /* batch number */, batchSize);
+
+ // Immediately attempting to fetch events again should fail.
+ executeDeviceTestMethod(".SecurityLoggingTest",
+ "testSecurityLoggingRetrievalRateLimited");
} finally {
// Always attempt to disable security logging to bring the device to initial state.
executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
}
}
+ private void runBatch(int batchNumber, int batchSize) throws Exception {
+ // Trigger security events of type TAG_ADB_SHELL_CMD.
+ for (int i = 0; i < batchSize; i++) {
+ getDevice().executeShellCommand("adb shell echo just_testing_" + i);
+ }
+
+ // Sleep for ~1 minute so that SecurityLogMonitor fetches the security events. This is
+ // an implementation detail we shouldn't rely on but there is no other way to do it
+ // currently.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(70));
+
+ // Verify the contents of the batch.
+ executeDeviceTestMethod(".SecurityLoggingTest", "testGetSecurityLogs",
+ Collections.singletonMap(ARG_SECURITY_LOGGING_BATCH_NUMBER,
+ Integer.toString(batchNumber)));
+ }
+
public void testNetworkLoggingWithTwoUsers() throws Exception {
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
@@ -343,11 +388,34 @@
if (!mHasFeature) {
return;
}
-
executeDeviceTestMethod(".NetworkLoggingTest", "testProvidingWrongBatchTokenReturnsNull");
- executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval");
+ executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+ Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
}
+ public void testNetworkLogging_multipleBatches() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+ Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(2)));
+ }
+
+ public void testNetworkLogging_rebootResetsId() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ // First batch: retrieve and verify the events.
+ executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+ Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
+ // Reboot the device, so the security event IDs are re-set.
+ rebootAndWaitUntilReady();
+ // First batch after reboot: retrieve and verify the events.
+ executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
+ Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
+ }
+
+
public void testSetAffiliationId_IllegalArgumentException() throws Exception {
if (!mHasFeature) {
return;
@@ -433,21 +501,8 @@
if (!mHasFeature || !canCreateAdditionalUsers(1)) {
return;
}
-
- final int userId = createUser();
- installAppAsUser(INTENT_RECEIVER_APK, userId);
- installAppAsUser(DEVICE_OWNER_APK, userId);
- setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
-
- switchUser(userId);
- wakeupAndDismissKeyguard();
-
- // Setting the same affiliation ids on both users and running the lock task tests.
- runDeviceTestsAsUser(
- DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
- runDeviceTestsAsUser(
- DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
- runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".LockTaskTest", userId);
+ final int userId = createAffiliatedSecondaryUser();
+ executeAffiliatedProfileOwnerTest("LockTaskTest", userId);
}
public void testSystemUpdatePolicy() throws Exception {
@@ -503,16 +558,18 @@
// Execute HardwarePropertiesManagerTest as a device owner.
public void testHardwarePropertiesManagerAsDeviceOwner() throws Exception {
- if (!mHasFeature)
+ if (!mHasFeature) {
return;
+ }
executeDeviceTestMethod(".HardwarePropertiesManagerTest", "testHardwarePropertiesManager");
}
// Execute VrTemperatureTest as a device owner.
public void testVrTemperaturesAsDeviceOwner() throws Exception {
- if (!mHasFeature)
+ if (!mHasFeature) {
return;
+ }
executeDeviceTestMethod(".VrTemperatureTest", "testVrTemperatures");
}
@@ -593,6 +650,92 @@
executeDeviceOwnerTest("BackupServiceEnabledTest");
}
+ public void testPackageInstallCache() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ final File apk = buildHelper.getTestFile(TEST_APP_APK);
+ try {
+ getDevice().uninstallPackage(TEST_APP_PKG);
+ assertTrue(getDevice().pushFile(apk, TEST_APP_LOCATION + apk.getName()));
+
+ // Install the package in primary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageInstall", mPrimaryUserId);
+
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testKeepPackageCache", mPrimaryUserId);
+
+ // Remove the package in primary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageUninstall", mPrimaryUserId);
+
+ // Should be able to enable the cached package in primary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testInstallExistingPackage", mPrimaryUserId);
+ } finally {
+ String command = "rm " + TEST_APP_LOCATION + apk.getName();
+ getDevice().executeShellCommand(command);
+ getDevice().uninstallPackage(TEST_APP_PKG);
+ }
+ }
+
+ public void testPackageInstallCache_multiUser() throws Exception {
+ if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+ return;
+ }
+ final int userId = createAffiliatedSecondaryUser();
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ final File apk = buildHelper.getTestFile(TEST_APP_APK);
+ try {
+ getDevice().uninstallPackage(TEST_APP_PKG);
+ assertTrue(getDevice().pushFile(apk, TEST_APP_LOCATION + apk.getName()));
+
+ // Install the package in primary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageInstall", mPrimaryUserId);
+
+ // Should be able to enable the package in secondary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testInstallExistingPackage", userId);
+
+ // Remove the package in both user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageUninstall", mPrimaryUserId);
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageUninstall", userId);
+
+ // Install the package in secondary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageInstall", userId);
+
+ // Should be able to enable the package in primary user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testInstallExistingPackage", mPrimaryUserId);
+
+ // Keep the package in cache
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testKeepPackageCache", mPrimaryUserId);
+
+ // Remove the package in both user
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageUninstall", mPrimaryUserId);
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testPackageUninstall", userId);
+
+ // Should be able to enable the cached package in both users
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testInstallExistingPackage", userId);
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PackageInstallTest",
+ "testInstallExistingPackage", mPrimaryUserId);
+ } finally {
+ String command = "rm " + TEST_APP_LOCATION + apk.getName();
+ getDevice().executeShellCommand(command);
+ getDevice().uninstallPackage(TEST_APP_PKG);
+ }
+ }
+
private void executeDeviceOwnerTest(String testClassName) throws Exception {
if (!mHasFeature) {
return;
@@ -601,6 +744,15 @@
runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, mPrimaryUserId);
}
+ private void executeAffiliatedProfileOwnerTest(String testClassName, int userId)
+ throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ String testClass = DEVICE_OWNER_PKG + "." + testClassName;
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, userId);
+ }
+
private void executeDeviceTestMethod(String className, String testName) throws Exception {
if (!mHasFeature) {
return;
@@ -608,4 +760,30 @@
runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
/* deviceOwnerUserId */ mPrimaryUserId);
}
-}
\ No newline at end of file
+
+ private int createAffiliatedSecondaryUser() throws Exception {
+ final int userId = createUser();
+ installAppAsUser(INTENT_RECEIVER_APK, userId);
+ installAppAsUser(DEVICE_OWNER_APK, userId);
+ setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
+
+ switchUser(userId);
+ wakeupAndDismissKeyguard();
+
+ // Setting the same affiliation ids on both users and running the lock task tests.
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
+ runDeviceTestsAsUser(
+ DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+ return userId;
+ }
+
+ private void executeDeviceTestMethod(String className, String testName,
+ Map<String, String> params) throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
+ /* deviceOwnerUserId */ mPrimaryUserId, params);
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
index df4a47c..84bdd7d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
@@ -17,7 +17,7 @@
package com.android.cts.devicepolicy;
/**
- * Tests for emphemeral users and profiles.
+ * Tests for ephemeral users and profiles.
*/
public class EphemeralUserTest extends BaseDevicePolicyTest {
@@ -35,7 +35,7 @@
/** The user should have the ephemeral flag set if it was created as ephemeral. */
public void testCreateEphemeralUser() throws Exception {
- if (!mHasFeature || !hasUserSplit()) {
+ if (!mHasFeature) {
return;
}
int userId = createUser(FLAG_EPHEMERAL);
@@ -59,7 +59,8 @@
*/
public void testProfileInheritsEphemeral() throws Exception {
if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")
- || !hasUserSplit() || !canCreateAdditionalUsers(2)) {
+ || !canCreateAdditionalUsers(2)
+ || !hasUserSplit()) {
return;
}
int userId = createUser(FLAG_EPHEMERAL);
@@ -72,7 +73,7 @@
* Ephemeral user should be automatically removed after it is stopped.
*/
public void testRemoveEphemeralOnStop() throws Exception {
- if (!mHasFeature || !hasUserSplit()) {
+ if (!mHasFeature) {
return;
}
int userId = createUser(FLAG_EPHEMERAL);
@@ -87,7 +88,7 @@
* and not ephemeral when the feature is not set.
*/
public void testEphemeralGuestFeature() throws Exception {
- if (!mHasFeature || !hasUserSplit()) {
+ if (!mHasFeature) {
return;
}
// Create a guest user.
@@ -103,20 +104,6 @@
}
}
- /**
- * Test that creating an ephemeral user fails on systems without the split system user.
- */
- public void testCreateEphemeralWithoutUserSplitFails() throws Exception {
- if (!mHasFeature || hasUserSplit()) {
- return;
- }
- String command ="pm create-user --ephemeral " + "TestUser_" + System.currentTimeMillis();
- String commandOutput = getDevice().executeShellCommand(command);
-
- assertEquals("Creating the epehemeral user should fail.",
- "Error: couldn't create User.", commandOutput.trim());
- }
-
private boolean getGuestUsersEphemeral() throws Exception {
String commandOutput = getDevice().executeShellCommand("dumpsys user");
String[] outputLines = commandOutput.split("\n");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index ef7099b..b71c4c87 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -15,8 +15,6 @@
*/
package com.android.cts.devicepolicy;
-import org.junit.Test;
-
/**
* This class tests the provisioning flow with an APK that declares a single receiver with
* BIND_DEVICE_ADMIN permissions, which was a requirement for the app sending the
@@ -54,7 +52,6 @@
super.tearDown();
}
- @Test
public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
if (!mHasFeature) {
return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
index fa3627a..93a17e0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
@@ -111,6 +111,17 @@
"testAccountExist", mParentUserId);
}
+ public void testWebview() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ // We start an activity containing WebView in another process and run provisioning to
+ // test that the process is not killed.
+ startActivityAsUser(mParentUserId, MANAGED_PROFILE_PKG, ".WebViewActivity");
+ provisionManagedProfile();
+ }
+
private void provisionManagedProfile() throws Exception {
runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
"testProvisionManagedProfile", mParentUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 157c59c..39825b5 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -47,7 +47,6 @@
private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
- private static final String WIFI_CONFIG_CREATOR_PKG = "com.android.cts.wificonfigcreator";
private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
@@ -65,8 +64,6 @@
private static final String NOTIFICATION_APK = "CtsNotificationSenderApp.apk";
private static final String NOTIFICATION_PKG =
"com.android.cts.managedprofiletests.notificationsender";
- private static final String NOTIFICATION_ACTIVITY =
- NOTIFICATION_PKG + ".SendNotification";
private static final String ADMIN_RECEIVER_TEST_CLASS =
MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
@@ -78,7 +75,6 @@
private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
private static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
- private static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.SECONDS.toMillis(30);
@@ -87,11 +83,14 @@
// Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
+ private static final String PROFILE_CREDENTIAL = "1234";
+ // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
+ private static final int PROFILE_TIMEOUT_DELAY_MS = 10_000;
+
private int mParentUserId;
// ID of the profile we'll create. This will always be a profile of the parent.
private int mProfileUserId;
- private String mPackageVerifier;
private boolean mHasNfcFeature;
@@ -138,6 +137,29 @@
mProfileUserId);
}
+ public void testWipeDataWithReason() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ assertTrue(listUsers().contains(mProfileUserId));
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".WipeDataTest",
+ "testWipeDataWithReason",
+ mProfileUserId);
+ // Note: the managed profile is removed by this test, which will make removeUserCommand in
+ // tearDown() to complain, but that should be OK since its result is not asserted.
+ assertUserGetsRemoved(mProfileUserId);
+ // testWipeDataWithReason() removes the managed profile,
+ // so it needs to separated from other tests.
+ // Check the notification is presented after work profile got removed, so profile user no
+ // longer exists, verification should be run in primary user.
+ runDeviceTestsAsUser(
+ MANAGED_PROFILE_PKG,
+ ".WipeDataWithReasonVerificationTest",
+ mParentUserId);
+ }
+
/**
* wipeData() test removes the managed profile, so it needs to separated from other tests.
*/
@@ -147,7 +169,8 @@
}
assertTrue(listUsers().contains(mProfileUserId));
runDeviceTestsAsUser(
- MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".WipeDataTest", mProfileUserId);
+ MANAGED_PROFILE_PKG, ".WipeDataTest",
+ "testWipeData", mProfileUserId);
// Note: the managed profile is removed by this test, which will make removeUserCommand in
// tearDown() to complain, but that should be OK since its result is not asserted.
assertUserGetsRemoved(mProfileUserId);
@@ -178,6 +201,84 @@
TIMEOUT_USER_LOCKED_MILLIS);
}
+ /** Profile should get locked if it is not in foreground no matter what. */
+ public void testWorkProfileTimeoutBackground() throws Exception {
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mPrimaryUserId, true);
+ simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(true);
+ }
+
+ /** Profile should get locked if it is in foreground but with no user activity. */
+ public void testWorkProfileTimeoutIdleActivity() throws Exception {
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mProfileUserId, false);
+ Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(true);
+ }
+
+ /** User activity in profile should prevent it from locking. */
+ public void testWorkProfileTimeoutUserActivity() throws Exception {
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mProfileUserId, false);
+ simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(false);
+ }
+
+ /** Keep screen on window flag in the profile should prevent it from locking. */
+ public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
+ setUpWorkProfileTimeout();
+
+ startDummyActivity(mProfileUserId, true);
+ Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+ verifyOnlyProfileLocked(false);
+ }
+
+ private void setUpWorkProfileTimeout() throws DeviceNotAvailableException {
+ // Set separate challenge.
+ changeUserCredential(PROFILE_CREDENTIAL, null, mProfileUserId);
+
+ // Make sure the profile is not prematurely locked.
+ verifyUserCredential(PROFILE_CREDENTIAL, mProfileUserId);
+ verifyOnlyProfileLocked(false);
+ // Set profile timeout to 5 seconds.
+ runProfileTimeoutTest("testSetWorkProfileTimeout", mProfileUserId);
+ }
+
+ private void verifyOnlyProfileLocked(boolean locked) throws DeviceNotAvailableException {
+ final String expectedResultTest = locked ? "testDeviceLocked" : "testDeviceNotLocked";
+ runProfileTimeoutTest(expectedResultTest, mProfileUserId);
+ // Primary profile shouldn't be locked.
+ runProfileTimeoutTest("testDeviceNotLocked", mPrimaryUserId);
+ }
+
+ private void simulateUserInteraction(int timeMs) throws Exception {
+ final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
+ for (int i = 0; i < timeMs; i += timeMs/10) {
+ Thread.sleep(timeMs/10);
+ helper.tapScreenCenter();
+ }
+ }
+
+ private void runProfileTimeoutTest(String method, int userId)
+ throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ProfileTimeoutTestHelper",
+ method, userId);
+ }
+
+ private void startDummyActivity(int profileUserId, boolean keepScreenOn) throws Exception {
+ getDevice().executeShellCommand(String.format(
+ "am start-activity -W --user %d --ez keep_screen_on %s %s/.TimeoutActivity",
+ profileUserId, keepScreenOn, MANAGED_PROFILE_PKG));
+ }
+
public void testMaxOneManagedProfile() throws Exception {
int newUserId = -1;
try {
@@ -1028,6 +1129,26 @@
}
}
+ public void testIsUsingUnifiedPassword() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+
+ // Freshly created profile profile has no separate challenge.
+ verifyUnifiedPassword(true);
+
+ // Set separate challenge and verify that the API reports it correctly.
+ changeUserCredential("1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+ verifyUnifiedPassword(false);
+ }
+
+ private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
+ final String testMethod =
+ unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
+ runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".IsUsingUnifiedPasswordTest",
+ testMethod, mProfileUserId);
+ }
+
private void disableActivityForUser(String activityName, int userId)
throws DeviceNotAvailableException {
String command = "am start -W --user " + userId
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTransferTest.java
new file mode 100644
index 0000000..6fbc087
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTransferTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.devicepolicy;
+
+/**
+ * Tests the DPC transfer functionality for device owner. Testing is done by having two dummy DPCs,
+ * CtsTransferOwnerOutgoingApp and CtsTransferOwnerIncomingApp. The former is the current DPC
+ * and the latter will be the new DPC after transfer. In order to run the tests from the correct
+ * process, first we setup some policies in the client side in CtsTransferOwnerOutgoingApp and then
+ * we verify the policies are still there in CtsTransferOwnerIncomingApp.
+ */
+public class MixedDeviceOwnerTransferTest extends DeviceAndProfileOwnerTransferTest {
+ private static final String TRANSFER_DEVICE_OWNER_OUTGOING_TEST =
+ "com.android.cts.transferowner.TransferDeviceOwnerOutgoingTest";
+ private static final String TRANSFER_DEVICE_OWNER_INCOMING_TEST =
+ "com.android.cts.transferowner.TransferDeviceOwnerIncomingTest";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (mHasFeature) {
+ setupDeviceOwner(TRANSFER_OWNER_OUTGOING_APK,
+ TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+ setupTestParameters(mPrimaryUserId, TRANSFER_DEVICE_OWNER_OUTGOING_TEST,
+ TRANSFER_DEVICE_OWNER_INCOMING_TEST);
+ }
+ }
+
+ private void setupDeviceOwner(String apkName, String adminReceiverClassName) throws Exception {
+ installAppAsUser(apkName, mPrimaryUserId);
+ setDeviceOwnerOrFail(adminReceiverClassName, mPrimaryUserId);
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTransferTest.java
new file mode 100644
index 0000000..3167729
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTransferTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.devicepolicy;
+
+/**
+ * Tests the DPC transfer functionality for profile owner. Testing is done by having two dummy DPCs,
+ * CtsTransferOwnerOutgoingApp and CtsTransferOwnerIncomingApp. The former is the current DPC
+ * and the latter will be the new DPC after transfer. In order to run the tests from the correct
+ * process, first we setup some policies in the client side in CtsTransferOwnerOutgoingApp and then
+ * we verify the policies are still there in CtsTransferOwnerIncomingApp.
+ */
+public class MixedProfileOwnerTransferTest extends DeviceAndProfileOwnerTransferTest {
+ private static final String TRANSFER_PROFILE_OWNER_OUTGOING_TEST =
+ "com.android.cts.transferowner.TransferProfileOwnerOutgoingTest";
+ private static final String TRANSFER_PROFILE_OWNER_INCOMING_TEST =
+ "com.android.cts.transferowner.TransferProfileOwnerIncomingTest";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // We need managed users to be supported in order to create a profile of the user owner.
+ mHasFeature &= hasDeviceFeature("android.software.managed_users");
+ if (mHasFeature) {
+ int profileOwnerUserId = setupManagedProfile(TRANSFER_OWNER_OUTGOING_APK,
+ TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+ setupTestParameters(profileOwnerUserId, TRANSFER_PROFILE_OWNER_OUTGOING_TEST,
+ TRANSFER_PROFILE_OWNER_INCOMING_TEST);
+ }
+ }
+
+ private int setupManagedProfile(String apkName, String adminReceiverClassName)
+ throws Exception {
+ final int userId = createManagedProfile(mPrimaryUserId);
+ installAppAsUser(apkName, userId);
+ setProfileOwnerOrFail(adminReceiverClassName, userId);
+ startUser(userId);
+ return userId;
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
new file mode 100644
index 0000000..e2f3f9c
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -0,0 +1,73 @@
+package com.android.cts.devicepolicy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * CTS to verify toggling quiet mode in work profile by using
+ * {@link android.os.UserManager#trySetQuietModeEnabled(boolean, android.os.UserHandle)}.
+ */
+public class QuietModeHostsideTest extends BaseDevicePolicyTest {
+ private static final String TEST_PACKAGE = "com.android.cts.launchertests";
+ private static final String TEST_CLASS = ".QuietModeTest";
+ private static final String PARAM_TARGET_USER = "TARGET_USER";
+ private static final String PARAM_ORIGINAL_DEFAULT_LAUNCHER = "ORIGINAL_DEFAULT_LAUNCHER";
+ private static final String TEST_APK = "CtsLauncherAppsTests.apk";
+
+ private static final String TEST_LAUNCHER_PACKAGE = "com.android.cts.launchertests.support";
+ private static final String TEST_LAUNCHER_APK = "CtsLauncherAppsTestsSupport.apk";
+
+ private int mProfileId;
+ private String mOriginalLauncher;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mHasFeature = mHasFeature & hasDeviceFeature("android.software.managed_users");
+
+ if(mHasFeature) {
+ mOriginalLauncher = getDefaultLauncher();
+
+ installAppAsUser(TEST_APK, mPrimaryUserId);
+ installAppAsUser(TEST_LAUNCHER_APK, mPrimaryUserId);
+
+ createAndStartManagedProfile();
+ installAppAsUser(TEST_APK, mProfileId);
+
+ waitForBroadcastIdle();
+ wakeupAndDismissKeyguard();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mHasFeature) {
+ getDevice().uninstallPackage(TEST_PACKAGE);
+ getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE);
+ }
+ super.tearDown();
+ }
+
+ public void testQuietMode() throws Exception {
+ runDeviceTestsAsUser(
+ TEST_PACKAGE,
+ TEST_CLASS,
+ null,
+ mPrimaryUserId,
+ createParams(mProfileId));
+ }
+
+ private void createAndStartManagedProfile() throws Exception {
+ mProfileId = createManagedProfile(mPrimaryUserId);
+ switchUser(mPrimaryUserId);
+ startUser(mProfileId);
+ }
+
+ private Map<String, String> createParams(int targetUserId) throws Exception {
+ Map<String, String> params = new HashMap<>();
+ params.put(PARAM_TARGET_USER, Integer.toString(getUserSerialNumber(targetUserId)));
+ params.put(PARAM_ORIGINAL_DEFAULT_LAUNCHER, mOriginalLauncher);
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserActivityEmulator.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserActivityEmulator.java
new file mode 100644
index 0000000..0b17b17
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserActivityEmulator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Helper to simulate user activity on the device.
+ */
+class UserActivityEmulator {
+ private final int mWidth;
+ private final int mHeight;
+ private final ITestDevice mDevice;
+
+ public UserActivityEmulator(ITestDevice device) throws DeviceNotAvailableException {
+ // Figure out screen size. Output is something like "Physical size: 1440x2880".
+ mDevice = device;
+ final String output = mDevice.executeShellCommand("wm size");
+ final String[] sizes = output.split(" ")[2].split("x");
+ mWidth = Integer.valueOf(sizes[0].trim());
+ mHeight = Integer.valueOf(sizes[1].trim());
+ }
+
+ public void tapScreenCenter() throws DeviceNotAvailableException {
+ mDevice.executeShellCommand(String.format("input tap %d %d", mWidth / 2, mHeight / 2));
+ }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/crossprofile/CrossProfileAppsManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/crossprofile/CrossProfileAppsManagedProfileTest.java
new file mode 100644
index 0000000..cf02aef
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/crossprofile/CrossProfileAppsManagedProfileTest.java
@@ -0,0 +1,8 @@
+//package com.android.cts.devicepolicy.crossprofile;
+//
+//import com.android.cts.devicepolicy.BaseDevicePolicyTest;
+//
+//public class CrossProfileAppsManagedProfileTest extends BaseDevicePolicyTest {
+//
+//
+//}
diff --git a/hostsidetests/dumpsys/AndroidTest.xml b/hostsidetests/dumpsys/AndroidTest.xml
index 252bf90..0ac7c2e 100644
--- a/hostsidetests/dumpsys/AndroidTest.xml
+++ b/hostsidetests/dumpsys/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS dumpsys host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest">
<option name="jar" value="CtsDumpsysHostTestCases.jar" />
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
index 1a84011..01b9ca8 100644
--- a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24"/>
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name=".MainActivity"
android:exported="true" />
<service android:name=".ProcStatsHelperServiceMain"
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 4d51330..2ae5265 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -442,7 +442,7 @@
}
private void checkBatteryDischarge(String[] parts) {
- assertEquals(12, parts.length);
+ assertEquals(14, parts.length);
assertInteger(parts[4]); // low
assertInteger(parts[5]); // high
assertInteger(parts[6]); // screenOn
@@ -451,6 +451,8 @@
assertInteger(parts[9]); // dischargeScreenOffMah
assertInteger(parts[10]); // dischargeDozeCount
assertInteger(parts[11]); // dischargeDozeMah
+ assertInteger(parts[12]); // dischargeLightDozeMah
+ assertInteger(parts[13]); // dischargeDeepDozeMah
}
private void checkBatteryLevel(String[] parts) {
@@ -723,7 +725,8 @@
for (int i = 0; i < TIMESTAMP_COUNT; i++) {
numparts[i] = assertInteger(parts[i]);
}
- if (numparts[0] != 0) {
+ // Flags = 1 just means the first frame of the window
+ if (numparts[0] != 0 && numparts[0] != 1) {
continue;
}
// assert VSYNC >= INTENDED_VSYNC
diff --git a/hostsidetests/edi/AndroidTest.xml b/hostsidetests/edi/AndroidTest.xml
index 9449d72..aef3086 100644
--- a/hostsidetests/edi/AndroidTest.xml
+++ b/hostsidetests/edi/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS EDI host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsEdiHostTestCases.jar" />
diff --git a/hostsidetests/gputools/Android.mk b/hostsidetests/gputools/Android.mk
new file mode 100644
index 0000000..b2946be
--- /dev/null
+++ b/hostsidetests/gputools/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MODULE := CtsGpuToolsHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+LOCAL_STATIC_JAVA_LIBRARIES := platform-test-annotations-host
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/gputools/AndroidTest.xml b/hostsidetests/gputools/AndroidTest.xml
new file mode 100644
index 0000000..98e41a7
--- /dev/null
+++ b/hostsidetests/gputools/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="Config for CtsGpuToolsHostTestCases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="misc" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-DEBUG.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-RELEASE.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-LAYERS.apk" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsGpuToolsHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/gputools/apps/Android.mk b/hostsidetests/gputools/apps/Android.mk
new file mode 100644
index 0000000..2a2cdd9
--- /dev/null
+++ b/hostsidetests/gputools/apps/Android.mk
@@ -0,0 +1,69 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libctsgputools_jni
+LOCAL_SRC_FILES := \
+ jni/CtsGpuToolsJniOnLoad.cpp \
+ jni/android_gputools_cts_RootlessGpuDebug.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := CtsGpuToolsRootlessGpuDebugApp-DEBUG
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+libctsgputools_jni
+
+LOCAL_AAPT_FLAGS := \
+--rename-manifest-package android.rootlessgpudebug.DEBUG.app \
+--debug-mode
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := CtsGpuToolsRootlessGpuDebugApp-RELEASE
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+libctsgputools_jni \
+libVkLayer_nullLayerC
+
+LOCAL_AAPT_FLAGS := \
+--rename-manifest-package android.rootlessgpudebug.RELEASE.app
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/gputools/apps/AndroidManifest.xml b/hostsidetests/gputools/apps/AndroidManifest.xml
new file mode 100755
index 0000000..de8130f
--- /dev/null
+++ b/hostsidetests/gputools/apps/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.rootlessgpudebug.app">>
+
+ <application>
+ <activity android:name=".RootlessGpuDebugDeviceActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
+
diff --git a/hostsidetests/gputools/apps/jni/CtsGpuToolsJniOnLoad.cpp b/hostsidetests/gputools/apps/jni/CtsGpuToolsJniOnLoad.cpp
new file mode 100644
index 0000000..2761aea
--- /dev/null
+++ b/hostsidetests/gputools/apps/jni/CtsGpuToolsJniOnLoad.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_gputools_cts_RootlessGpuDebug(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+ JNIEnv* env = nullptr;
+ if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
+ return JNI_ERR;
+ if (register_android_gputools_cts_RootlessGpuDebug(env))
+ return JNI_ERR;
+ return JNI_VERSION_1_4;
+}
diff --git a/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
new file mode 100644
index 0000000..4fdddbc
--- /dev/null
+++ b/hostsidetests/gputools/apps/jni/android_gputools_cts_RootlessGpuDebug.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#define LOG_TAG "RootlessGpuDebug"
+
+#include <android/log.h>
+#include <jni.h>
+#include <string>
+#include <vulkan/vulkan.h>
+
+#define ALOGI(msg, ...) \
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
+#define ALOGE(msg, ...) \
+ __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, (msg), __VA_ARGS__)
+
+namespace {
+
+std::string initVulkan()
+{
+ std::string result = "";
+
+ const VkApplicationInfo app_info = {
+ VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ nullptr, // pNext
+ "RootlessGpuDebug", // app name
+ 0, // app version
+ nullptr, // engine name
+ 0, // engine version
+ VK_API_VERSION_1_0,
+ };
+ const VkInstanceCreateInfo instance_info = {
+ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ nullptr, // pNext
+ 0, // flags
+ &app_info,
+ 0, // layer count
+ nullptr, // layers
+ 0, // extension count
+ nullptr, // extensions
+ };
+ VkInstance instance;
+ VkResult vkResult = vkCreateInstance(&instance_info, nullptr, &instance);
+ if (vkResult == VK_ERROR_INITIALIZATION_FAILED) {
+ result = "vkCreateInstance failed, meaning layers could not be chained.";
+ } else {
+ result = "vkCreateInstance succeeded.";
+ }
+
+ return result;
+}
+
+jstring android_gputools_cts_RootlessGpuDebug_nativeInitVulkan(JNIEnv* env,
+ jclass /*clazz*/)
+{
+ std::string result;
+
+ result = initVulkan();
+
+ return env->NewStringUTF(result.c_str());
+}
+
+static JNINativeMethod gMethods[] = {
+ { "nativeInitVulkan", "()Ljava/lang/String;",
+ (void*) android_gputools_cts_RootlessGpuDebug_nativeInitVulkan },
+};
+
+} // anonymous namespace
+
+int register_android_gputools_cts_RootlessGpuDebug(JNIEnv* env) {
+ jclass clazz = env->FindClass("android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity");
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
new file mode 100644
index 0000000..ec5052b
--- /dev/null
+++ b/hostsidetests/gputools/apps/src/android/rootlessgpudebug/app/RootlessGpuDebugDeviceActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.rootlessgpudebug.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+public class RootlessGpuDebugDeviceActivity extends Activity {
+
+ static {
+ System.loadLibrary("ctsgputools_jni");
+ }
+
+ private static final String TAG = RootlessGpuDebugDeviceActivity.class.getSimpleName();
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ String result = nativeInitVulkan();
+ Log.i(TAG, result);
+ }
+
+ private static native String nativeInitVulkan();
+
+}
+
diff --git a/hostsidetests/gputools/layers/Android.mk b/hostsidetests/gputools/layers/Android.mk
new file mode 100644
index 0000000..69f6930
--- /dev/null
+++ b/hostsidetests/gputools/layers/Android.mk
@@ -0,0 +1,70 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libVkLayer_nullLayerA
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := jni/nullLayer.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME="A"
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libVkLayer_nullLayerB
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := jni/nullLayer.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME="B"
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libVkLayer_nullLayerC
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := jni/nullLayer.cpp
+LOCAL_CFLAGS += -std=c++14 -Wall -Werror -fvisibility=hidden -DLAYERNAME="C"
+LOCAL_SHARED_LIBRARIES := libandroid libvulkan liblog
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_SDK_VERSION := current
+include $(BUILD_SHARED_LIBRARY)
+
+
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_PACKAGE_NAME := CtsGpuToolsRootlessGpuDebugApp-LAYERS
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+libVkLayer_nullLayerA \
+libVkLayer_nullLayerB \
+libVkLayer_nullLayerC
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/gputools/layers/AndroidManifest.xml b/hostsidetests/gputools/layers/AndroidManifest.xml
new file mode 100755
index 0000000..957b047
--- /dev/null
+++ b/hostsidetests/gputools/layers/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.rootlessgpudebug.LAYERS.app">
+
+ <application android:debuggable="false" android:hasCode="false">
+ </application>
+
+</manifest>
+
+
diff --git a/hostsidetests/gputools/layers/jni/nullLayer.cpp b/hostsidetests/gputools/layers/jni/nullLayer.cpp
new file mode 100644
index 0000000..7253b5c
--- /dev/null
+++ b/hostsidetests/gputools/layers/jni/nullLayer.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <cstring>
+#include <vulkan/vulkan.h>
+#include "vk_layer_interface.h"
+
+#define xstr(a) str(a)
+#define str(a) #a
+
+#define LOG_TAG "nullLayer" xstr(LAYERNAME)
+
+#define ALOGI(msg, ...) \
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
+
+
+// Announce if anything loads this layer. LAYERNAME is defined in Android.mk
+class StaticLogMessage {
+ public:
+ StaticLogMessage(const char* msg) {
+ ALOGI("%s", msg);
+ }
+};
+StaticLogMessage g_initMessage("nullLayer" xstr(LAYERNAME) " loaded");
+
+
+namespace {
+
+
+// Minimal dispatch table for this simple layer
+struct {
+ PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
+} g_VulkanDispatchTable;
+
+
+template<class T>
+VkResult getProperties(const uint32_t count, const T *properties, uint32_t *pCount,
+ T *pProperties) {
+ uint32_t copySize;
+
+ if (pProperties == NULL || properties == NULL) {
+ *pCount = count;
+ return VK_SUCCESS;
+ }
+
+ copySize = *pCount < count ? *pCount : count;
+ memcpy(pProperties, properties, copySize * sizeof(T));
+ *pCount = copySize;
+ if (copySize < count) {
+ return VK_INCOMPLETE;
+ }
+
+ return VK_SUCCESS;
+}
+
+static const VkLayerProperties LAYER_PROPERTIES = {
+ "VK_LAYER_ANDROID_nullLayer" xstr(LAYERNAME), VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION), 1, "Layer: nullLayer" xstr(LAYERNAME),
+};
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceLayerProperties(uint32_t *pCount, VkLayerProperties *pProperties) {
+ return getProperties<VkLayerProperties>(1, &LAYER_PROPERTIES, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceLayerProperties(VkPhysicalDevice /* physicalDevice */, uint32_t *pCount,
+ VkLayerProperties *pProperties) {
+ return getProperties<VkLayerProperties>(0, NULL, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char* /* pLayerName */, uint32_t *pCount,
+ VkExtensionProperties *pProperties) {
+ return getProperties<VkExtensionProperties>(0, NULL, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice /* physicalDevice */, const char* /* pLayerName */,
+ uint32_t *pCount, VkExtensionProperties *pProperties) {
+ return getProperties<VkExtensionProperties>(0, NULL, pCount, pProperties);
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL nullCreateInstance(const VkInstanceCreateInfo* pCreateInfo,
+ const VkAllocationCallbacks* pAllocator,
+ VkInstance* pInstance) {
+
+ VkLayerInstanceCreateInfo *layerCreateInfo = (VkLayerInstanceCreateInfo *)pCreateInfo->pNext;
+
+ const char* msg = "nullCreateInstance called in nullLayer" xstr(LAYERNAME);
+ ALOGI("%s", msg);
+
+ // Step through the pNext chain until we get to the link function
+ while(layerCreateInfo && (layerCreateInfo->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO ||
+ layerCreateInfo->function != VK_LAYER_FUNCTION_LINK)) {
+
+ layerCreateInfo = (VkLayerInstanceCreateInfo *)layerCreateInfo->pNext;
+ }
+
+ if(layerCreateInfo == NULL)
+ return VK_ERROR_INITIALIZATION_FAILED;
+
+ // Grab GIPA for the next layer
+ PFN_vkGetInstanceProcAddr gpa = layerCreateInfo->u.pLayerInfo->pfnNextGetInstanceProcAddr;
+
+ // Track is in our dispatch table
+ g_VulkanDispatchTable.GetInstanceProcAddr = gpa;
+
+ // Advance the chain for next layer
+ layerCreateInfo->u.pLayerInfo = layerCreateInfo->u.pLayerInfo->pNext;
+
+ // Call the next layer
+ PFN_vkCreateInstance createFunc = (PFN_vkCreateInstance)gpa(VK_NULL_HANDLE, "vkCreateInstance");
+ VkResult ret = createFunc(pCreateInfo, pAllocator, pInstance);
+
+ return ret;
+}
+
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice /* dev */, const char* /* funcName */) {
+ return nullptr;
+}
+
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* funcName) {
+
+ // Our simple layer only intercepts vkCreateInstance
+ const char* targetFunc = "vkCreateInstance";
+ if (!strncmp(targetFunc, funcName, sizeof(&targetFunc)))
+ return (PFN_vkVoidFunction)nullCreateInstance;
+
+ return (PFN_vkVoidFunction)g_VulkanDispatchTable.GetInstanceProcAddr(instance, funcName);
+}
+
+} // namespace
+
+// loader-layer interface v0, just wrappers since there is only a layer
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(uint32_t *pCount,
+ VkLayerProperties *pProperties) {
+ return EnumerateInstanceLayerProperties(pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(VkPhysicalDevice physicalDevice, uint32_t *pCount,
+ VkLayerProperties *pProperties) {
+ return EnumerateDeviceLayerProperties(physicalDevice, pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pCount,
+ VkExtensionProperties *pProperties) {
+ return EnumerateInstanceExtensionProperties(pLayerName, pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice,
+ const char *pLayerName, uint32_t *pCount,
+ VkExtensionProperties *pProperties) {
+ return EnumerateDeviceExtensionProperties(physicalDevice, pLayerName, pCount, pProperties);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev, const char *funcName) {
+ return GetDeviceProcAddr(dev, funcName);
+}
+
+__attribute((visibility("default"))) VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char *funcName) {
+ return GetInstanceProcAddr(instance, funcName);
+}
diff --git a/hostsidetests/gputools/layers/jni/vk_layer_interface.h b/hostsidetests/gputools/layers/jni/vk_layer_interface.h
new file mode 100644
index 0000000..f2a5232
--- /dev/null
+++ b/hostsidetests/gputools/layers/jni/vk_layer_interface.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2015-2016 The Khronos Group Inc.
+ * Copyright (c) 2015-2016 Valve Corporation
+ * Copyright (c) 2015-2016 LunarG, Inc.
+ * Copyright (c) 2016 Google Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and/or associated documentation files (the "Materials"), to
+ * deal in the Materials without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Materials, and to permit persons to whom the Materials are
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice(s) and this permission notice shall be included in
+ * all copies or substantial portions of the Materials.
+ *
+ * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ *
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
+ * USE OR OTHER DEALINGS IN THE MATERIALS.
+ *
+ */
+#pragma once
+
+#include <vulkan/vulkan.h>
+
+// ------------------------------------------------------------------------------------------------
+// CreateInstance and CreateDevice support structures
+
+typedef enum VkLayerFunction_ {
+ VK_LAYER_FUNCTION_LINK = 0,
+ VK_LAYER_FUNCTION_DATA_CALLBACK = 1
+} VkLayerFunction;
+
+typedef struct VkLayerInstanceLink_ {
+ struct VkLayerInstanceLink_* pNext;
+ PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+} VkLayerInstanceLink;
+
+typedef VkResult(VKAPI_PTR* PFN_vkSetInstanceLoaderData)(VkInstance instance,
+ void* object);
+typedef VkResult(VKAPI_PTR* PFN_vkSetDeviceLoaderData)(VkDevice device,
+ void* object);
+
+typedef struct {
+ VkStructureType sType; // VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO
+ const void* pNext;
+ VkLayerFunction function;
+ union {
+ VkLayerInstanceLink* pLayerInfo;
+ PFN_vkSetInstanceLoaderData pfnSetInstanceLoaderData;
+ } u;
+} VkLayerInstanceCreateInfo;
+
+typedef struct VkLayerDeviceLink_ {
+ struct VkLayerDeviceLink_* pNext;
+ PFN_vkGetInstanceProcAddr pfnNextGetInstanceProcAddr;
+ PFN_vkGetDeviceProcAddr pfnNextGetDeviceProcAddr;
+} VkLayerDeviceLink;
+
+typedef struct {
+ VkStructureType sType; // VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO
+ const void* pNext;
+ VkLayerFunction function;
+ union {
+ VkLayerDeviceLink* pLayerInfo;
+ PFN_vkSetDeviceLoaderData pfnSetDeviceLoaderData;
+ } u;
+} VkLayerDeviceCreateInfo;
diff --git a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
new file mode 100644
index 0000000..96096fb
--- /dev/null
+++ b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.gputools.cts;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import com.android.ddmlib.Log;
+
+import java.util.Scanner;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that exercise Rootless GPU Debug functionality supported by the loader.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsRootlessGpuDebugHostTest implements IDeviceTest {
+
+ public static final String TAG = CtsRootlessGpuDebugHostTest.class.getSimpleName();
+
+ /**
+ * A reference to the device under test.
+ */
+ private ITestDevice mDevice;
+
+ public void setDevice(ITestDevice device) {
+ mDevice = device;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mDevice;
+ }
+
+ // This test ensures that the Vulkan loader can use Settings to load layer
+ // from the base directory of debuggable applications. Is also tests several
+ // positive and negative scenarios we want to cover (listed below).
+ //
+ // There are three APKs; DEBUG and RELEASE are practically identical with one
+ // being flagged as debuggable. The LAYERS APK is mainly a conduit for getting
+ // layers onto the device without affecting the other APKs.
+ //
+ // The RELEASE APK does contain one layer to ensure using Settings to enable
+ // layers does not interfere with legacy methods using system properties.
+ //
+ // The layers themselves are practically null, only enough functionality to
+ // satisfy loader enumerating and loading. They don't actually chain together.
+ //
+ // Positive tests
+ // - Ensure we can toggle the Enable Setting on and off (testDebugLayerLoadVulkan)
+ // - Ensure we can set the debuggable app (testDebugLayerLoadVulkan)
+ // - Ensure we can set the layer list (testDebugLayerLoadVulkan)
+ // - Ensure we can push a layer to debuggable app (testDebugLayerLoadVulkan)
+ // - Ensure we can specify the app to load layers (testDebugLayerLoadVulkan)
+ // - Ensure we can load a layer from app's data directory (testDebugLayerLoadVulkan)
+ // - Ensure we can load multiple layers, in order, from app's data directory (testDebugLayerLoadVulkan)
+ // - Ensure we can still use system properties if no layers loaded via Settings (testSystemPropertyEnableVulkan)
+ // Negative tests
+ // - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoad)
+ // - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoad)
+ // - Ensure we cannot enumerate layers from debuggable app's data directory if Setting not specified (testDebugNoEnumerateVulkan)
+ // - Ensure we cannot enumerate layers without specifying the debuggable app (testDebugNoEnumerateVulkan)
+ // - Ensure we cannot use system properties when layer is found via Settings with debuggable app (testSystemPropertyIgnoreVulkan)
+
+ private static final String CLASS = "RootlessGpuDebugDeviceActivity";
+ private static final String ACTIVITY = "android.rootlessgpudebug.app.RootlessGpuDebugDeviceActivity";
+ private static final String LAYER_A = "nullLayerA";
+ private static final String LAYER_B = "nullLayerB";
+ private static final String LAYER_C = "nullLayerC";
+ private static final String LAYER_A_LIB = "libVkLayer_" + LAYER_A + ".so";
+ private static final String LAYER_B_LIB = "libVkLayer_" + LAYER_B + ".so";
+ private static final String LAYER_C_LIB = "libVkLayer_" + LAYER_C + ".so";
+ private static final String LAYER_A_NAME = "VK_LAYER_ANDROID_" + LAYER_A;
+ private static final String LAYER_B_NAME = "VK_LAYER_ANDROID_" + LAYER_B;
+ private static final String LAYER_C_NAME = "VK_LAYER_ANDROID_" + LAYER_C;
+ private static final String DEBUG_APP = "android.rootlessgpudebug.DEBUG.app";
+ private static final String RELEASE_APP = "android.rootlessgpudebug.RELEASE.app";
+ private static final String LAYERS_APP = "android.rootlessgpudebug.LAYERS.app";
+
+ // This is how long we'll scan the log for a result before giving up. This limit will only
+ // be reached if something has gone wrong
+ private static final long LOG_SEARCH_TIMEOUT_MS = 5000;
+
+ private String removeWhitespace(String input) {
+ return input.replaceAll(System.getProperty("line.separator"), "").trim();
+ }
+
+ /**
+ * Grab and format the process ID of requested app.
+ */
+ private String getPID(String app) throws Exception {
+ String pid = mDevice.executeAdbCommand("shell", "pidof", app);
+ return removeWhitespace(pid);
+ }
+
+ /**
+ * Extract the requested layer from APK and copy to tmp
+ */
+ private void setupLayer(String layer) throws Exception {
+
+ // We use the LAYERS apk to facilitate getting layers onto the device for mixing and matching
+ String libPath = mDevice.executeAdbCommand("shell", "pm", "path", LAYERS_APP);
+ libPath = libPath.replaceAll("package:", "");
+ libPath = libPath.replaceAll("base.apk", "");
+ libPath = removeWhitespace(libPath);
+ libPath += "lib/";
+
+ // Use find to get the .so so we can ignore ABI
+ String layerPath = mDevice.executeAdbCommand("shell", "find", libPath + " -name " + layer);
+ layerPath = removeWhitespace(layerPath);
+ mDevice.executeAdbCommand("shell", "cp", layerPath + " /data/local/tmp");
+ }
+
+ /**
+ * Simple helper class for returning multiple results
+ */
+ public class LogScanResult {
+ public boolean found;
+ public int lineNumber;
+ }
+
+ private LogScanResult scanLog(String pid, String searchString) throws Exception {
+ return scanLog(pid, searchString, "");
+ }
+
+ /**
+ * Scan the logcat for requested process ID, returning if found and which line
+ */
+ private LogScanResult scanLog(String pid, String searchString, String endString) throws Exception {
+
+ LogScanResult result = new LogScanResult();
+ result.found = false;
+ result.lineNumber = -1;
+
+ // Scan until output from app is found
+ boolean scanComplete= false;
+
+ // Let the test run a reasonable amount of time before moving on
+ long hostStartTime = System.currentTimeMillis();
+
+ while (!scanComplete && ((System.currentTimeMillis() - hostStartTime) < LOG_SEARCH_TIMEOUT_MS)) {
+
+ // Give our activity a chance to run and fill the log
+ Thread.sleep(1000);
+
+ // Pull the logcat for a single process
+ String logcat = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "--pid=" + pid, "*:V");
+ int lineNumber = 0;
+ Scanner apkIn = new Scanner(logcat);
+ while (apkIn.hasNextLine()) {
+ lineNumber++;
+ String line = apkIn.nextLine();
+ if (line.contains(searchString) && line.endsWith(endString)) {
+ result.found = true;
+ result.lineNumber = lineNumber;
+ }
+ if (line.contains("vkCreateInstance succeeded")) {
+ // Once we've got output from the app, we've collected what we need
+ scanComplete= true;
+ }
+ }
+ apkIn.close();
+ }
+
+ return result;
+ }
+
+ /**
+ * Remove any temporary files on the device, clear any settings, kill the apps after each test
+ */
+ @After
+ public void cleanup() throws Exception {
+ mDevice.executeAdbCommand("shell", "am", "force-stop", DEBUG_APP);
+ mDevice.executeAdbCommand("shell", "am", "force-stop", RELEASE_APP);
+ mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_A_LIB);
+ mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_B_LIB);
+ mDevice.executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_C_LIB);
+ mDevice.executeAdbCommand("shell", "settings", "delete", "global", "enable_gpu_debug_layers");
+ mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_app");
+ mDevice.executeAdbCommand("shell", "settings", "delete", "global", "gpu_debug_layers");
+ mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers", "\'\"\"\'");
+ }
+
+ /**
+ * This is the primary test of the feature. It pushes layers to our debuggable app and ensures they are
+ * loaded in the correct order.
+ */
+ @Test
+ public void testDebugLayerLoadVulkan() throws Exception {
+
+ // Set up layers to be loaded
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME + ":" + LAYER_B_NAME);
+
+ // Copy the layers from our LAYERS APK to tmp
+ setupLayer(LAYER_A_LIB);
+ setupLayer(LAYER_B_LIB);
+
+ // Copy them over to our DEBUG app
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_B_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_B_LIB, ";", "chmod", "700", LAYER_B_LIB + "\'");
+
+ // Kick off our DEBUG app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(DEBUG_APP);
+
+ // Check that both layers were loaded, in the correct order
+ String searchStringA = "nullCreateInstance called in " + LAYER_A;
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertTrue("LayerA was not loaded", resultA.found);
+
+ String searchStringB = "nullCreateInstance called in " + LAYER_B;
+ LogScanResult resultB = scanLog(pid, searchStringB);
+ Assert.assertTrue("LayerB was not loaded", resultB.found);
+
+ Assert.assertTrue("LayerA should be loaded before LayerB", resultA.lineNumber < resultB.lineNumber);
+ }
+
+ /**
+ * This test ensures that we cannot push a layer to a non-debuggable app
+ * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+ */
+ @Test
+ public void testReleaseLayerLoadVulkan() throws Exception {
+
+ // Set up a layers to be loaded for RELEASE app
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME + ":" + LAYER_B_NAME);
+
+ // Copy a layer from our LAYERS APK to tmp
+ setupLayer(LAYER_A_LIB);
+
+ // Attempt to copy them over to our RELEASE app (this should fail)
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", RELEASE_APP,
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'", "||", "echo", "run-as", "failed");
+
+ // Kick off our RELEASE app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(RELEASE_APP);
+
+ // Ensure we don't load the layer in base dir
+ String searchStringA = LAYER_A_NAME + "loaded";
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertFalse("LayerA was enumerated", resultA.found);
+ }
+
+ /**
+ * This test ensures debuggable apps do not enumerate layers in base
+ * directory if enable_gpu_debug_layers is not enabled.
+ */
+ @Test
+ public void testDebugNotEnabledVulkan() throws Exception {
+
+ // Ensure the global layer enable settings is NOT enabled
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "0");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME);
+
+ // Copy a layer from our LAYERS APK to tmp
+ setupLayer(LAYER_A_LIB);
+
+ // Copy it over to our DEBUG app
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+
+ // Kick off our DEBUG app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(DEBUG_APP);
+
+ // Ensure we don't load the layer in base dir
+ String searchStringA = LAYER_A_NAME + "loaded";
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertFalse("LayerA was enumerated", resultA.found);
+ }
+
+ /**
+ * This test ensures debuggable apps do not enumerate layers in base
+ * directory if gpu_debug_app does not match.
+ */
+ @Test
+ public void testDebugWrongAppVulkan() throws Exception {
+
+ // Ensure the gpu_debug_app does not match what we launch
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME);
+
+ // Copy a layer from our LAYERS APK to tmp
+ setupLayer(LAYER_A_LIB);
+
+ // Copy it over to our DEBUG app
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+
+ // Kick off our DEBUG app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(DEBUG_APP);
+
+ // Ensure we don't load the layer in base dir
+ String searchStringA = LAYER_A_NAME + "loaded";
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertFalse("LayerA was enumerated", resultA.found);
+ }
+
+ /**
+ * This test ensures debuggable apps do not enumerate layers in base
+ * directory if gpu_debug_layers are not set.
+ */
+ @Test
+ public void testDebugNoLayersEnabledVulkan() throws Exception {
+
+ // Ensure the global layer enable settings is NOT enabled
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", "foo");
+
+ // Copy a layer from our LAYERS APK to tmp
+ setupLayer(LAYER_A_LIB);
+
+ // Copy it over to our DEBUG app
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+
+ // Kick off our DEBUG app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(DEBUG_APP);
+
+ // Ensure layerA is not loaded
+ String searchStringA = "nullCreateInstance called in " + LAYER_A;
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertFalse("LayerA was loaded", resultA.found);
+ }
+
+ /**
+ * This test ensures we can still use properties if no layer found via Settings
+ */
+ @Test
+ public void testSystemPropertyEnableVulkan() throws Exception {
+
+ // Set up layerA to be loaded, but not layerB or layerC
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", RELEASE_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME);
+
+ // Enable layerC (which is packaged with the RELEASE app) with system properties
+ mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers " + LAYER_C_NAME);
+
+ // Kick off our RELEASE app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(RELEASE_APP);
+
+ // Check that both layers were loaded, in the correct order
+ String searchStringA = LAYER_A_NAME + "loaded";
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertFalse("LayerA was enumerated", resultA.found);
+
+ String searchStringC = "nullCreateInstance called in " + LAYER_C;
+ LogScanResult resultC = scanLog(pid, searchStringC);
+ Assert.assertTrue("LayerC was not loaded", resultC.found);
+ }
+
+ /**
+ * This test ensures system properties are ignored if Settings load a layer
+ */
+ @Test
+ public void testSystemPropertyIgnoreVulkan() throws Exception {
+
+ // Set up layerA to be loaded, but not layerB
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "enable_gpu_debug_layers", "1");
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_app", DEBUG_APP);
+ mDevice.executeAdbCommand("shell", "settings", "put", "global", "gpu_debug_layers", LAYER_A_NAME);
+
+ // Copy the layers from our LAYERS APK
+ setupLayer(LAYER_A_LIB);
+ setupLayer(LAYER_B_LIB);
+
+ // Copy them over to our DEBUG app
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'");
+ mDevice.executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_B_LIB, "|", "run-as", DEBUG_APP,
+ "sh", "-c", "\'cat", ">", LAYER_B_LIB, ";", "chmod", "700", LAYER_B_LIB + "\'");
+
+ // Enable layerB with system properties
+ mDevice.executeAdbCommand("shell", "setprop", "debug.vulkan.layers " + LAYER_B_NAME);
+
+ // Kick off our DEBUG app
+ mDevice.executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+
+ // Give it a chance to start, then grab process ID
+ Thread.sleep(1000);
+ String pid = getPID(DEBUG_APP);
+
+ // Ensure only layerA is loaded
+ String searchStringA = "nullCreateInstance called in " + LAYER_A;
+ LogScanResult resultA = scanLog(pid, searchStringA);
+ Assert.assertTrue("LayerA was not loaded", resultA.found);
+
+ String searchStringB = "nullCreateInstance called in " + LAYER_B;
+ LogScanResult resultB = scanLog(pid, searchStringB);
+ Assert.assertFalse("LayerB was loaded", resultB.found);
+ }
+}
diff --git a/hostsidetests/incident/AndroidTest.xml b/hostsidetests/incident/AndroidTest.xml
index f26092e..641b516 100644
--- a/hostsidetests/incident/AndroidTest.xml
+++ b/hostsidetests/incident/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Incident host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="metrics" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsIncidentHostTestCases.jar" />
diff --git a/hostsidetests/incident/apps/boundwidgetapp/Android.mk b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
index 152414f..5430d47 100644
--- a/hostsidetests/incident/apps/boundwidgetapp/Android.mk
+++ b/hostsidetests/incident/apps/boundwidgetapp/Android.mk
@@ -24,7 +24,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
diff --git a/hostsidetests/incident/apps/procstatsapp/Android.mk b/hostsidetests/incident/apps/procstatsapp/Android.mk
new file mode 100644
index 0000000..a02c29d
--- /dev/null
+++ b/hostsidetests/incident/apps/procstatsapp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsProcStatsProtoApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ctstestrunner \
+ compatibility-device-util \
+ android-support-v4
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/incident/apps/procstatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/procstatsapp/AndroidManifest.xml
new file mode 100644
index 0000000..a0cccb4
--- /dev/null
+++ b/hostsidetests/incident/apps/procstatsapp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.cts.procstats" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".SimpleActivity" android:exported="true" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.cts.procstats" />
+</manifest>
diff --git a/hostsidetests/incident/apps/procstatsapp/src/com/android/server/cts/procstats/SimpleActivity.java b/hostsidetests/incident/apps/procstatsapp/src/com/android/server/cts/procstats/SimpleActivity.java
new file mode 100644
index 0000000..69ff75e
--- /dev/null
+++ b/hostsidetests/incident/apps/procstatsapp/src/com/android/server/cts/procstats/SimpleActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.procstats;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+public class SimpleActivity extends Activity {
+
+ private static final String TAG = "ProcstatsAppRunningTest";
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ Log.i(TAG, "Procstats app is running");
+ }
+}
+
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
new file mode 100644
index 0000000..a3dc1d6
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import com.android.server.AlarmClockMetadataProto;
+import com.android.server.AlarmManagerServiceProto;
+import com.android.server.AlarmProto;
+import com.android.server.BatchProto;
+import com.android.server.BroadcastStatsProto;
+import com.android.server.ConstantsProto;
+import com.android.server.FilterStatsProto;
+import com.android.server.ForceAppStandbyTrackerProto;
+import com.android.server.IdleDispatchEntryProto;
+import com.android.server.InFlightProto;
+import com.android.server.WakeupEventProto;
+import java.util.List;
+
+/**
+ * Test to check that the alarm manager service properly outputs its dump state.
+ */
+public class AlarmManagerIncidentTest extends ProtoDumpTestCase {
+ public void testAlarmManagerServiceDump() throws Exception {
+ final AlarmManagerServiceProto dump =
+ getDump(AlarmManagerServiceProto.parser(), "dumpsys alarm --proto");
+
+ // Times should be positive.
+ assertTrue(0 < dump.getCurrentTime());
+ assertTrue(0 < dump.getElapsedRealtime());
+ assertTrue(0 < dump.getLastTimeChangeClockTime());
+ assertTrue(0 < dump.getLastTimeChangeRealtime());
+
+ // ConstantsProto
+ ConstantsProto settings = dump.getSettings();
+ assertTrue(0 < settings.getMinFuturityDurationMs());
+ assertTrue(0 < settings.getMinIntervalDurationMs());
+ assertTrue(0 < settings.getListenerTimeoutDurationMs());
+ assertTrue(0 < settings.getAllowWhileIdleShortDurationMs());
+ assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
+ assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
+
+ // ForceAppStandbyTrackerProto
+ ForceAppStandbyTrackerProto forceAppStandbyTracker = dump.getForceAppStandbyTracker();
+ for (int uid : forceAppStandbyTracker.getForegroundUidsList()) {
+ // 0 is technically a valid UID.
+ assertTrue(0 <= uid);
+ }
+ for (int aid : forceAppStandbyTracker.getPowerSaveWhitelistAppIdsList()) {
+ assertTrue(0 <= aid);
+ }
+ for (int aid : forceAppStandbyTracker.getTempPowerSaveWhitelistAppIdsList()) {
+ assertTrue(0 <= aid);
+ }
+ for (ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages r : forceAppStandbyTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
+ assertTrue(0 <= r.getUid());
+ }
+
+ if (!dump.getIsInteractive()) {
+ // These are only valid if is_interactive is false.
+ assertTrue(0 < dump.getTimeSinceNonInteractiveMs());
+ assertTrue(0 < dump.getMaxWakeupDelayMs());
+ assertTrue(0 < dump.getTimeSinceLastDispatchMs());
+ // time_until_next_non_wakeup_delivery_ms could be negative if the delivery time is in the past.
+ }
+
+ assertTrue(0 < dump.getTimeUntilNextNonWakeupAlarmMs());
+ assertTrue(0 < dump.getTimeUntilNextWakeupMs());
+ assertTrue(0 < dump.getTimeSinceLastWakeupMs());
+ assertTrue(0 < dump.getTimeSinceLastWakeupSetMs());
+ assertTrue(0 <= dump.getTimeChangeEventCount());
+
+ for (int aid : dump.getDeviceIdleUserWhitelistAppIdsList()) {
+ assertTrue(0 <= aid);
+ }
+
+ // AlarmClockMetadataProto
+ for (AlarmClockMetadataProto ac : dump.getNextAlarmClockMetadataList()) {
+ assertTrue(0 <= ac.getUser());
+ assertTrue(0 < ac.getTriggerTimeMs());
+ }
+
+ for (BatchProto b : dump.getPendingAlarmBatchesList()) {
+ final long start = b.getStartRealtime();
+ final long end = b.getEndRealtime();
+ assertTrue("Batch start time (" + start+ ") is negative", 0 <= start);
+ assertTrue("Batch end time (" + end + ") is negative", 0 <= end);
+ assertTrue("Batch start time (" + start + ") is after its end time (" + end + ")",
+ start <= end);
+ testAlarmProtoList(b.getAlarmsList());
+ }
+
+ testAlarmProtoList(dump.getPendingUserBlockedBackgroundAlarmsList());
+
+ testAlarmProto(dump.getPendingIdleUntil());
+
+ testAlarmProtoList(dump.getPendingWhileIdleAlarmsList());
+
+ testAlarmProto(dump.getNextWakeFromIdle());
+
+ testAlarmProtoList(dump.getPastDueNonWakeupAlarmsList());
+
+ assertTrue(0 <= dump.getDelayedAlarmCount());
+ assertTrue(0 <= dump.getTotalDelayTimeMs());
+ assertTrue(0 <= dump.getMaxDelayDurationMs());
+ assertTrue(0 <= dump.getMaxNonInteractiveDurationMs());
+
+ assertTrue(0 <= dump.getBroadcastRefCount());
+ assertTrue(0 <= dump.getPendingIntentSendCount());
+ assertTrue(0 <= dump.getPendingIntentFinishCount());
+ assertTrue(0 <= dump.getListenerSendCount());
+ assertTrue(0 <= dump.getListenerFinishCount());
+
+ for (InFlightProto f : dump.getOutstandingDeliveriesList()) {
+ assertTrue(0 <= f.getUid());
+ assertTrue(0 < f.getWhenElapsedMs());
+ testBroadcastStatsProto(f.getBroadcastStats());
+ testFilterStatsProto(f.getFilterStats());
+ }
+
+ long awimds = dump.getAllowWhileIdleMinDurationMs();
+ assertTrue(awimds == settings.getAllowWhileIdleShortDurationMs()
+ || awimds == settings.getAllowWhileIdleLongDurationMs());
+
+ for (AlarmManagerServiceProto.LastAllowWhileIdleDispatch l : dump.getLastAllowWhileIdleDispatchTimesList()) {
+ assertTrue(0 <= l.getUid());
+ assertTrue(0 < l.getTimeMs());
+ }
+
+ for (AlarmManagerServiceProto.TopAlarm ta : dump.getTopAlarmsList()) {
+ assertTrue(0 <= ta.getUid());
+ testFilterStatsProto(ta.getFilter());
+ }
+
+ for (AlarmManagerServiceProto.AlarmStat as : dump.getAlarmStatsList()) {
+ testBroadcastStatsProto(as.getBroadcast());
+ for (FilterStatsProto f : as.getFiltersList()) {
+ testFilterStatsProto(f);
+ }
+ }
+
+ for (IdleDispatchEntryProto id : dump.getAllowWhileIdleDispatchesList()) {
+ assertTrue(0 <= id.getUid());
+ assertTrue(0 <= id.getEntryCreationRealtime());
+ assertTrue(0 <= id.getArgRealtime());
+ }
+
+ for (WakeupEventProto we : dump.getRecentWakeupHistoryList()) {
+ assertTrue(0 <= we.getUid());
+ assertTrue(0 <= we.getWhen());
+ }
+ }
+
+ private void testAlarmProtoList(List<AlarmProto> alarms) throws Exception {
+ for (AlarmProto a : alarms) {
+ testAlarmProto(a);
+ }
+ }
+
+ private void testAlarmProto(AlarmProto alarm) throws Exception {
+ assertNotNull(alarm);
+
+ // alarm.time_until_when_elapsed_ms can be negative if 'when' is in the past.
+ assertTrue(0 <= alarm.getWindowLengthMs());
+ assertTrue(0 <= alarm.getRepeatIntervalMs());
+ assertTrue(0 <= alarm.getCount());
+ }
+
+ private void testBroadcastStatsProto(BroadcastStatsProto broadcast) throws Exception {
+ assertNotNull(broadcast);
+
+ assertTrue(0 <= broadcast.getUid());
+ assertTrue(0 <= broadcast.getTotalFlightDurationMs());
+ assertTrue(0 <= broadcast.getCount());
+ assertTrue(0 <= broadcast.getWakeupCount());
+ assertTrue(0 <= broadcast.getStartTimeRealtime());
+ // Nesting should be non-negative.
+ assertTrue(0 <= broadcast.getNesting());
+ }
+
+ private void testFilterStatsProto(FilterStatsProto filter) throws Exception {
+ assertNotNull(filter);
+
+ assertTrue(0 <= filter.getLastFlightTimeRealtime());
+ assertTrue(0 <= filter.getTotalFlightDurationMs());
+ assertTrue(0 <= filter.getCount());
+ assertTrue(0 <= filter.getWakeupCount());
+ assertTrue(0 <= filter.getStartTimeRealtime());
+ // Nesting should be non-negative.
+ assertTrue(0 <= filter.getNesting());
+ }
+}
+
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
index 4b83b0a..28000e0 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
@@ -16,6 +16,7 @@
package com.android.server.cts;
+import android.os.BatteryManagerProto;
import android.service.battery.BatteryServiceDumpProto;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -37,10 +38,10 @@
return;
}
- assertTrue(
- dump.getPlugged()
- != BatteryServiceDumpProto.BatteryPlugged.BATTERY_PLUGGED_WIRELESS);
- assertTrue(dump.getChargeCounter() > 0);
+ assertTrue(dump.getPlugged() != BatteryManagerProto.PlugType.PLUG_TYPE_WIRELESS);
+ assertTrue(dump.getMaxChargingCurrent() >= 0);
+ assertTrue(dump.getMaxChargingVoltage() >= 0);
+ assertTrue(dump.getChargeCounter() >= 0);
assertTrue(
dump.getStatus() != BatteryServiceDumpProto.BatteryStatus.BATTERY_STATUS_INVALID);
assertTrue(
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java
new file mode 100644
index 0000000..fb971de
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import android.os.BatteryStatsProto;
+import android.os.ControllerActivityProto;
+import android.os.SystemProto;
+import android.os.TimerProto;
+import android.os.UidProto;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
+
+/**
+ * Test to BatteryStats proto dump.
+ */
+public class BatteryStatsIncidentTest extends ProtoDumpTestCase {
+
+ @Override
+ protected void tearDown() throws Exception {
+ batteryOffScreenOn();
+ super.tearDown();
+ }
+
+ protected void batteryOnScreenOff() throws Exception {
+ getDevice().executeShellCommand("dumpsys battery unplug");
+ getDevice().executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+ }
+
+ protected void batteryOffScreenOn() throws Exception {
+ getDevice().executeShellCommand("dumpsys battery reset");
+ getDevice().executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+ }
+
+ /**
+ * Tests that batterystats is dumped to proto with sane values.
+ */
+ public void testBatteryStatsServiceDump() throws Exception {
+ batteryOnScreenOff();
+ Thread.sleep(5000); // Allow some time for battery data to accumulate.
+
+ final BatteryStatsServiceDumpProto dump = getDump(BatteryStatsServiceDumpProto.parser(),
+ "dumpsys batterystats --proto");
+ final BatteryStatsProto bs = dump.getBatterystats();
+ assertNotNull(bs);
+
+ // Proto dumps were finalized when the batterystats report version was ~29 and the parcel
+ // version was ~172.
+ assertTrue(29 <= bs.getReportVersion());
+ assertTrue(172 <= bs.getParcelVersion());
+ assertNotNull(bs.getStartPlatformVersion());
+ assertFalse(bs.getStartPlatformVersion().isEmpty());
+ assertNotNull(bs.getEndPlatformVersion());
+ assertFalse(bs.getEndPlatformVersion().isEmpty());
+
+ for (UidProto u : bs.getUidsList()) {
+ testUidProto(u);
+ }
+
+ testSystemProto(bs.getSystem());
+
+ batteryOffScreenOn();
+ }
+
+ private void testControllerActivityProto(ControllerActivityProto ca) throws Exception {
+ assertNotNull(ca);
+
+ assertTrue(0 <= ca.getIdleDurationMs());
+ assertTrue(0 <= ca.getRxDurationMs());
+ assertTrue(0 <= ca.getPowerMah());
+ for (ControllerActivityProto.TxLevel tx : ca.getTxList()) {
+ assertTrue(0 <= tx.getDurationMs());
+ }
+ }
+
+ private void testBatteryLevelStep(SystemProto.BatteryLevelStep bls) throws Exception {
+ assertNotNull(bls);
+
+ assertTrue(0 < bls.getDurationMs());
+ assertTrue(0 <= bls.getLevel());
+ assertTrue(100 >= bls.getLevel());
+
+ assertTrue(SystemProto.BatteryLevelStep.DisplayState.getDescriptor().getValues()
+ .contains(bls.getDisplayState().getValueDescriptor()));
+ assertTrue(SystemProto.BatteryLevelStep.PowerSaveMode.getDescriptor().getValues()
+ .contains(bls.getPowerSaveMode().getValueDescriptor()));
+ assertTrue(SystemProto.BatteryLevelStep.IdleMode.getDescriptor().getValues()
+ .contains(bls.getIdleMode().getValueDescriptor()));
+ }
+
+ private void testSystemProto(SystemProto s) throws Exception {
+ assertNotNull(s);
+
+ SystemProto.Battery b = s.getBattery();
+ assertTrue(0 < b.getStartClockTimeMs());
+ assertTrue(0 <= b.getStartCount());
+ long totalRealtimeMs = b.getTotalRealtimeMs();
+ long totalUptimeMs = b.getTotalUptimeMs();
+ assertTrue(0 <= totalUptimeMs);
+ assertTrue(totalUptimeMs <= totalRealtimeMs);
+ long batteryRealtimeMs = b.getBatteryRealtimeMs();
+ long batteryUptimeMs = b.getBatteryUptimeMs();
+ assertTrue(0 <= batteryUptimeMs);
+ assertTrue(batteryUptimeMs <= batteryRealtimeMs);
+ assertTrue(batteryRealtimeMs <= totalRealtimeMs);
+ assertTrue(batteryUptimeMs <= totalUptimeMs);
+ long screenOffRealtimeMs = b.getScreenOffRealtimeMs();
+ long screenOffUptimeMs = b.getScreenOffUptimeMs();
+ assertTrue(0 <= screenOffUptimeMs);
+ assertTrue(screenOffUptimeMs <= screenOffRealtimeMs);
+ assertTrue(screenOffRealtimeMs <= totalRealtimeMs);
+ assertTrue(screenOffUptimeMs <= totalUptimeMs);
+ long screenDozeDurationMs = b.getScreenDozeDurationMs();
+ assertTrue(0 <= screenDozeDurationMs);
+ assertTrue(screenDozeDurationMs <= screenOffRealtimeMs);
+ assertTrue(0 < b.getEstimatedBatteryCapacityMah());
+ long minLearnedCapacityUah = b.getMinLearnedBatteryCapacityUah();
+ long maxLearnedCapacityUah = b.getMaxLearnedBatteryCapacityUah();
+ assertTrue(0 <= minLearnedCapacityUah);
+ assertTrue(minLearnedCapacityUah <= maxLearnedCapacityUah);
+
+ SystemProto.BatteryDischarge bd = s.getBatteryDischarge();
+ int lowerBound = bd.getLowerBoundSinceCharge();
+ int upperBound = bd.getUpperBoundSinceCharge();
+ assertTrue(0 <= lowerBound);
+ assertTrue(lowerBound <= upperBound);
+ assertTrue(0 <= bd.getScreenOnSinceCharge());
+ int screenOff = bd.getScreenOffSinceCharge();
+ int screenDoze = bd.getScreenDozeSinceCharge();
+ assertTrue(0 <= screenDoze);
+ assertTrue(screenDoze <= screenOff);
+ long totalMah = bd.getTotalMah();
+ long totalMahScreenOff = bd.getTotalMahScreenOff();
+ long totalMahScreenDoze = bd.getTotalMahScreenDoze();
+ long totalMahLightDoze = bd.getTotalMahLightDoze();
+ long totalMahDeepDoze = bd.getTotalMahDeepDoze();
+ assertTrue(0 <= totalMahScreenDoze);
+ assertTrue(0 <= totalMahLightDoze);
+ assertTrue(0 <= totalMahDeepDoze);
+ assertTrue(totalMahScreenDoze <= totalMahScreenOff);
+ assertTrue(totalMahLightDoze <= totalMahScreenOff);
+ assertTrue(totalMahDeepDoze <= totalMahScreenOff);
+ assertTrue(totalMahScreenOff <= totalMah);
+
+ assertTrue(-1 <= s.getChargeTimeRemainingMs());
+ assertTrue(-1 <= s.getDischargeTimeRemainingMs());
+
+ for (SystemProto.BatteryLevelStep bls : s.getChargeStepList()) {
+ testBatteryLevelStep(bls);
+ }
+ for (SystemProto.BatteryLevelStep bls : s.getDischargeStepList()) {
+ testBatteryLevelStep(bls);
+ }
+
+ for (SystemProto.DataConnection dc : s.getDataConnectionList()) {
+ assertTrue(SystemProto.DataConnection.Name.getDescriptor().getValues()
+ .contains(dc.getName().getValueDescriptor()));
+ testTimerProto(dc.getTotal());
+ }
+
+ testControllerActivityProto(s.getGlobalBluetoothController());
+ testControllerActivityProto(s.getGlobalModemController());
+ testControllerActivityProto(s.getGlobalWifiController());
+
+ SystemProto.GlobalNetwork gn = s.getGlobalNetwork();
+ assertTrue(0 <= gn.getMobileBytesRx());
+ assertTrue(0 <= gn.getMobileBytesTx());
+ assertTrue(0 <= gn.getWifiBytesRx());
+ assertTrue(0 <= gn.getWifiBytesTx());
+ assertTrue(0 <= gn.getMobilePacketsRx());
+ assertTrue(0 <= gn.getMobilePacketsTx());
+ assertTrue(0 <= gn.getWifiPacketsRx());
+ assertTrue(0 <= gn.getWifiPacketsTx());
+ assertTrue(0 <= gn.getBtBytesRx());
+ assertTrue(0 <= gn.getBtBytesTx());
+
+ SystemProto.GlobalWifi gw = s.getGlobalWifi();
+ assertTrue(0 <= gw.getOnDurationMs());
+ assertTrue(0 <= gw.getRunningDurationMs());
+
+ for (SystemProto.KernelWakelock kw : s.getKernelWakelockList()) {
+ testTimerProto(kw.getTotal());
+ }
+
+ SystemProto.Misc m = s.getMisc();
+ assertTrue(0 <= m.getScreenOnDurationMs());
+ assertTrue(0 <= m.getPhoneOnDurationMs());
+ assertTrue(0 <= m.getFullWakelockTotalDurationMs());
+ assertTrue(0 <= m.getPartialWakelockTotalDurationMs());
+ assertTrue(0 <= m.getMobileRadioActiveDurationMs());
+ assertTrue(0 <= m.getMobileRadioActiveAdjustedTimeMs());
+ assertTrue(0 <= m.getMobileRadioActiveCount());
+ assertTrue(0 <= m.getMobileRadioActiveUnknownDurationMs());
+ assertTrue(0 <= m.getInteractiveDurationMs());
+ assertTrue(0 <= m.getBatterySaverModeEnabledDurationMs());
+ assertTrue(0 <= m.getNumConnectivityChanges());
+ assertTrue(0 <= m.getDeepDozeEnabledDurationMs());
+ assertTrue(0 <= m.getDeepDozeCount());
+ assertTrue(0 <= m.getDeepDozeIdlingDurationMs());
+ assertTrue(0 <= m.getDeepDozeIdlingCount());
+ assertTrue(0 <= m.getLongestDeepDozeDurationMs());
+ assertTrue(0 <= m.getLightDozeEnabledDurationMs());
+ assertTrue(0 <= m.getLightDozeCount());
+ assertTrue(0 <= m.getLightDozeIdlingDurationMs());
+ assertTrue(0 <= m.getLightDozeIdlingCount());
+ assertTrue(0 <= m.getLongestLightDozeDurationMs());
+
+ for (SystemProto.PhoneSignalStrength pss : s.getPhoneSignalStrengthList()) {
+ testTimerProto(pss.getTotal());
+ }
+
+ for (SystemProto.PowerUseItem pui : s.getPowerUseItemList()) {
+ assertTrue(SystemProto.PowerUseItem.Sipper.getDescriptor().getValues()
+ .contains(pui.getName().getValueDescriptor()));
+ assertTrue(0 <= pui.getUid());
+ assertTrue(0 <= pui.getComputedPowerMah());
+ assertTrue(0 <= pui.getScreenPowerMah());
+ assertTrue(0 <= pui.getProportionalSmearMah());
+ }
+
+ SystemProto.PowerUseSummary pus = s.getPowerUseSummary();
+ assertTrue(0 < pus.getBatteryCapacityMah());
+ assertTrue(0 <= pus.getComputedPowerMah());
+ double minDrained = pus.getMinDrainedPowerMah();
+ double maxDrained = pus.getMaxDrainedPowerMah();
+ assertTrue(0 <= minDrained);
+ assertTrue(minDrained <= maxDrained);
+
+ for (SystemProto.ResourcePowerManager rpm : s.getResourcePowerManagerList()) {
+ assertNotNull(rpm.getName());
+ assertFalse(rpm.getName().isEmpty());
+ testTimerProto(rpm.getTotal());
+ testTimerProto(rpm.getScreenOff());
+ }
+
+ for (SystemProto.ScreenBrightness sb : s.getScreenBrightnessList()) {
+ testTimerProto(sb.getTotal());
+ }
+
+ testTimerProto(s.getSignalScanning());
+
+ for (SystemProto.WakeupReason wr : s.getWakeupReasonList()) {
+ testTimerProto(wr.getTotal());
+ }
+
+ SystemProto.WifiMulticastWakelockTotal wmwl = s.getWifiMulticastWakelockTotal();
+ assertTrue(0 <= wmwl.getDurationMs());
+ assertTrue(0 <= wmwl.getCount());
+
+ for (SystemProto.WifiSignalStrength wss : s.getWifiSignalStrengthList()) {
+ testTimerProto(wss.getTotal());
+ }
+
+ for (SystemProto.WifiState ws : s.getWifiStateList()) {
+ assertTrue(SystemProto.WifiState.Name.getDescriptor().getValues()
+ .contains(ws.getName().getValueDescriptor()));
+ testTimerProto(ws.getTotal());
+ }
+
+ for (SystemProto.WifiSupplicantState wss : s.getWifiSupplicantStateList()) {
+ assertTrue(SystemProto.WifiSupplicantState.Name.getDescriptor().getValues()
+ .contains(wss.getName().getValueDescriptor()));
+ testTimerProto(wss.getTotal());
+ }
+ }
+
+ private void testTimerProto(TimerProto t) throws Exception {
+ assertNotNull(t);
+
+ long duration = t.getDurationMs();
+ long curDuration = t.getCurrentDurationMs();
+ long maxDuration = t.getMaxDurationMs();
+ long totalDuration = t.getTotalDurationMs();
+ assertTrue(0 <= duration);
+ assertTrue(0 <= t.getCount());
+ // Not all TimerProtos will have max duration, current duration, or total duration
+ // populated, so must tread carefully. Regardless, they should never be negative.
+ assertTrue(0 <= curDuration);
+ assertTrue(0 <= maxDuration);
+ assertTrue(0 <= totalDuration);
+ if (maxDuration > 0) {
+ assertTrue(curDuration <= maxDuration);
+ }
+ if (totalDuration > 0) {
+ assertTrue(maxDuration <= totalDuration);
+ assertTrue("Duration " + duration + " is greater than totalDuration " + totalDuration,
+ duration <= totalDuration);
+ }
+ }
+
+ private void testByFrequency(UidProto.Cpu.ByFrequency bf) throws Exception {
+ assertNotNull(bf);
+
+ assertTrue(1 <= bf.getFrequencyIndex());
+ long total = bf.getTotalDurationMs();
+ long screenOff = bf.getScreenOffDurationMs();
+ assertTrue(0 <= screenOff);
+ assertTrue(screenOff <= total);
+ }
+
+ private void testUidProto(UidProto u) throws Exception {
+ assertNotNull(u);
+
+ assertTrue(0 <= u.getUid());
+
+ for (UidProto.Package p : u.getPackagesList()) {
+ assertNotNull(p.getName());
+ assertFalse(p.getName().isEmpty());
+
+ for (UidProto.Package.Service s : p.getServicesList()) {
+ assertNotNull(s.getName());
+ assertFalse(s.getName().isEmpty());
+ assertTrue(0 <= s.getStartDurationMs());
+ assertTrue(0 <= s.getStartCount());
+ assertTrue(0 <= s.getLaunchCount());
+ }
+ }
+
+ testControllerActivityProto(u.getBluetoothController());
+ testControllerActivityProto(u.getModemController());
+ testControllerActivityProto(u.getWifiController());
+
+ UidProto.BluetoothMisc bm = u.getBluetoothMisc();
+ testTimerProto(bm.getApportionedBleScan());
+ testTimerProto(bm.getBackgroundBleScan());
+ testTimerProto(bm.getUnoptimizedBleScan());
+ testTimerProto(bm.getBackgroundUnoptimizedBleScan());
+ assertTrue(0 <= bm.getBleScanResultCount());
+ assertTrue(0 <= bm.getBackgroundBleScanResultCount());
+
+ UidProto.Cpu c = u.getCpu();
+ assertTrue(0 <= c.getUserDurationMs());
+ assertTrue(0 <= c.getSystemDurationMs());
+ for (UidProto.Cpu.ByFrequency bf : c.getByFrequencyList()) {
+ testByFrequency(bf);
+ }
+ for (UidProto.Cpu.ByProcessState bps : c.getByProcessStateList()) {
+ assertTrue(UidProto.Cpu.ProcessState.getDescriptor().getValues()
+ .contains(bps.getProcessState().getValueDescriptor()));
+ for (UidProto.Cpu.ByFrequency bf : bps.getByFrequencyList()) {
+ testByFrequency(bf);
+ }
+ }
+
+ testTimerProto(u.getAudio());
+ testTimerProto(u.getCamera());
+ testTimerProto(u.getFlashlight());
+ testTimerProto(u.getForegroundActivity());
+ testTimerProto(u.getForegroundService());
+ testTimerProto(u.getVibrator());
+ testTimerProto(u.getVideo());
+
+ for (UidProto.Job j : u.getJobsList()) {
+ assertNotNull(j.getName());
+ assertFalse(j.getName().isEmpty());
+ testTimerProto(j.getTotal());
+ testTimerProto(j.getBackground());
+ }
+
+ for (UidProto.JobCompletion jc : u.getJobCompletionList()) {
+ assertNotNull(jc.getName());
+ assertFalse(jc.getName().isEmpty());
+ for (UidProto.JobCompletion.ReasonCount rc : jc.getReasonCountList()) {
+ assertTrue(0 <= rc.getCount());
+ }
+ }
+
+ UidProto.Network n = u.getNetwork();
+ assertTrue(0 <= n.getMobileBytesRx());
+ assertTrue(0 <= n.getMobileBytesTx());
+ assertTrue(0 <= n.getWifiBytesRx());
+ assertTrue(0 <= n.getWifiBytesTx());
+ assertTrue(0 <= n.getBtBytesRx());
+ assertTrue(0 <= n.getBtBytesTx());
+ assertTrue(0 <= n.getMobilePacketsRx());
+ assertTrue(0 <= n.getMobilePacketsTx());
+ assertTrue(0 <= n.getWifiPacketsRx());
+ assertTrue(0 <= n.getWifiPacketsTx());
+ assertTrue(0 <= n.getMobileActiveDurationMs());
+ assertTrue(0 <= n.getMobileActiveCount());
+ assertTrue(0 <= n.getMobileWakeupCount());
+ assertTrue(0 <= n.getWifiWakeupCount());
+ assertTrue(0 <= n.getMobileBytesBgRx());
+ assertTrue(0 <= n.getMobileBytesBgTx());
+ assertTrue(0 <= n.getWifiBytesBgRx());
+ assertTrue(0 <= n.getWifiBytesBgTx());
+ assertTrue(0 <= n.getMobilePacketsBgRx());
+ assertTrue(0 <= n.getMobilePacketsBgTx());
+ assertTrue(0 <= n.getWifiPacketsBgRx());
+ assertTrue(0 <= n.getWifiPacketsBgTx());
+
+ UidProto.PowerUseItem pui = u.getPowerUseItem();
+ assertTrue(0 <= pui.getComputedPowerMah());
+ assertTrue(0 <= pui.getScreenPowerMah());
+ assertTrue(0 <= pui.getProportionalSmearMah());
+
+ for (UidProto.Process p : u.getProcessList()) {
+ assertNotNull(p.getName());
+ assertFalse(p.getName().isEmpty());
+ assertTrue(0 <= p.getUserDurationMs());
+ assertTrue(0 <= p.getSystemDurationMs());
+ assertTrue(0 <= p.getForegroundDurationMs());
+ assertTrue(0 <= p.getStartCount());
+ assertTrue(0 <= p.getAnrCount());
+ assertTrue(0 <= p.getCrashCount());
+ }
+
+ for (UidProto.StateTime st : u.getStatesList()) {
+ assertTrue(UidProto.StateTime.State.getDescriptor().getValues()
+ .contains(st.getState().getValueDescriptor()));
+ assertTrue(0 <= st.getDurationMs());
+ }
+
+ for (UidProto.Sensor s : u.getSensorsList()) {
+ testTimerProto(s.getApportioned());
+ testTimerProto(s.getBackground());
+ }
+
+ for (UidProto.Sync s : u.getSyncsList()) {
+ testTimerProto(s.getTotal());
+ testTimerProto(s.getBackground());
+ }
+
+ for (UidProto.UserActivity ua : u.getUserActivityList()) {
+ assertTrue(0 <= ua.getCount());
+ }
+
+ UidProto.AggregatedWakelock aw = u.getAggregatedWakelock();
+ long awPartial = aw.getPartialDurationMs();
+ long awBgPartial = aw.getBackgroundPartialDurationMs();
+ assertTrue(0 <= awBgPartial);
+ assertTrue(awBgPartial <= awPartial);
+
+ for (UidProto.Wakelock w : u.getWakelocksList()) {
+ testTimerProto(w.getFull());
+ testTimerProto(w.getPartial());
+ testTimerProto(w.getBackgroundPartial());
+ testTimerProto(w.getWindow());
+ }
+
+ for (UidProto.WakeupAlarm wa : u.getWakeupAlarmList()) {
+ assertTrue(0 <= wa.getCount());
+ }
+
+ UidProto.Wifi w = u.getWifi();
+ assertTrue(0 <= w.getFullWifiLockDurationMs());
+ assertTrue(0 <= w.getRunningDurationMs());
+ testTimerProto(w.getApportionedScan());
+ testTimerProto(w.getBackgroundScan());
+
+ testTimerProto(u.getWifiMulticastWakelock());
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 57d64bb..537a335 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
@@ -15,14 +15,9 @@
*/
package com.android.server.cts;
-import com.android.ddmlib.IShellOutputReceiver;
import com.android.tradefed.log.LogUtil;
-import com.google.common.base.Charsets;
-
import java.util.Random;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Test for "dumpsys batterystats -c
@@ -53,9 +48,9 @@
private static final int STATE_TIME_TOP_INDEX = 4;
private static final int STATE_TIME_FOREGROUND_SERVICE_INDEX = 5;
- private static final int STATE_TIME_FOREGROUND_INDEX = 7;
- private static final int STATE_TIME_BACKGROUND_INDEX = 8;
- private static final int STATE_TIME_CACHED_INDEX = 9;
+ private static final int STATE_TIME_FOREGROUND_INDEX = 6;
+ private static final int STATE_TIME_BACKGROUND_INDEX = 7;
+ private static final int STATE_TIME_CACHED_INDEX = 10;
private static final long TIME_SPENT_IN_TOP = 2000;
private static final long TIME_SPENT_IN_FOREGROUND = 2000;
@@ -272,7 +267,7 @@
} else if (keyguardStateLines && line.contains("showing=")) {
screenAwake &= line.trim().endsWith("false");
} else if (keyguardStateLines && line.contains("screenState=")) {
- screenAwake &= line.trim().endsWith("2");
+ screenAwake &= line.trim().endsWith("SCREEN_STATE_ON");
}
}
Thread.sleep(SCREEN_STATE_POLLING_INTERVAL);
@@ -710,64 +705,6 @@
}
/**
- * Runs logcat and waits (for a maximumum of maxTimeMs) until the desired text is displayed with
- * the given tag.
- * Logcat is not cleared, so make sure that text is unique (won't get false hits from old data).
- * Note that, in practice, the actual max wait time seems to be about 10s longer than maxTimeMs.
- */
- private void checkLogcatForText(String logcatTag, String text, int maxTimeMs) {
- IShellOutputReceiver receiver = new IShellOutputReceiver() {
- private final StringBuilder mOutputBuffer = new StringBuilder();
- private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
-
- @Override
- public void addOutput(byte[] data, int offset, int length) {
- if (!isCancelled()) {
- synchronized (mOutputBuffer) {
- String s = new String(data, offset, length, Charsets.UTF_8);
- mOutputBuffer.append(s);
- if (checkBufferForText()) {
- mIsCanceled.set(true);
- }
- }
- }
- }
-
- private boolean checkBufferForText() {
- if (mOutputBuffer.indexOf(text) > -1) {
- return true;
- } else {
- // delete all old data (except the last few chars) since they don't contain text
- // (presumably large chunks of data will be added at a time, so this is
- // sufficiently efficient.)
- int newStart = mOutputBuffer.length() - text.length();
- if (newStart > 0) {
- mOutputBuffer.delete(0, newStart);
- }
- return false;
- }
- }
-
- @Override
- public boolean isCancelled() {
- return mIsCanceled.get();
- }
-
- @Override
- public void flush() {
- }
- };
-
- try {
- // Wait for at most maxTimeMs for logcat to display the desired text.
- getDevice().executeShellCommand(String.format("logcat -s %s -e '%s'", logcatTag, text),
- receiver, maxTimeMs, TimeUnit.MILLISECONDS, 0);
- } catch (com.android.tradefed.device.DeviceNotAvailableException e) {
- System.err.println(e);
- }
- }
-
- /**
* Returns the bytes downloaded for the wifi transfer download tests.
* @param requestCode the output of executeForeground() or executeBackground() to identify in
* the logcat the line associated with the desired download information
diff --git a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
index 9e32e1c..ed87f11 100644
--- a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
@@ -16,9 +16,9 @@
package com.android.server.cts;
-import android.service.fingerprint.FingerprintActionStatsProto;
-import android.service.fingerprint.FingerprintServiceDumpProto;
-import android.service.fingerprint.FingerprintUserStatsProto;
+import com.android.server.fingerprint.FingerprintServiceDumpProto;
+import com.android.server.fingerprint.FingerprintUserStatsProto;
+import com.android.server.fingerprint.PerformanceStatsProto;
import com.android.tradefed.log.LogUtil.CLog;
@@ -27,12 +27,7 @@
* Test to check that the fingerprint service properly outputs its dump state.
*/
public class FingerprintIncidentTest extends ProtoDumpTestCase {
- /**
- * Test that no fingerprints are registered.
- *
- * @throws Exception
- */
- public void testNoneRegistered() throws Exception {
+ public void testFingerprintServiceDump() throws Exception {
// If the device doesn't support fingerprints, then pass.
if (!getDevice().hasFeature("android.hardware.fingerprint")) {
CLog.d("Bypass as android.hardware.fingerprint is not supported.");
@@ -42,24 +37,28 @@
final FingerprintServiceDumpProto dump =
getDump(FingerprintServiceDumpProto.parser(), "dumpsys fingerprint --proto");
- // One of them
- assertEquals(1, dump.getUsersCount());
+ // There should be at least one user.
+ assertTrue(1 <= dump.getUsersCount());
- final FingerprintUserStatsProto userStats = dump.getUsers(0);
- assertEquals(0, userStats.getUserId());
- assertEquals(0, userStats.getNumFingerprints());
+ for (int i = 0; i < dump.getUsersCount(); ++i) {
+ final FingerprintUserStatsProto userStats = dump.getUsers(i);
+ assertTrue(0 <= userStats.getUserId());
+ assertTrue(0 <= userStats.getNumFingerprints());
- final FingerprintActionStatsProto normal = userStats.getNormal();
- assertEquals(0, normal.getAccept());
- assertEquals(0, normal.getReject());
- assertEquals(0, normal.getAcquire());
- assertEquals(0, normal.getLockout());
+ final PerformanceStatsProto normal = userStats.getNormal();
+ assertTrue(0 <= normal.getAccept());
+ assertTrue(0 <= normal.getReject());
+ assertTrue(0 <= normal.getAcquire());
+ assertTrue(0 <= normal.getLockout());
+ assertTrue(0 <= normal.getPermanentLockout());
- final FingerprintActionStatsProto crypto = userStats.getCrypto();
- assertEquals(0, crypto.getAccept());
- assertEquals(0, crypto.getReject());
- assertEquals(0, crypto.getAcquire());
- assertEquals(0, crypto.getLockout());
+ final PerformanceStatsProto crypto = userStats.getCrypto();
+ assertTrue(0 <= crypto.getAccept());
+ assertTrue(0 <= crypto.getReject());
+ assertTrue(0 <= crypto.getAcquire());
+ assertTrue(0 <= crypto.getLockout());
+ assertTrue(0 <= crypto.getPermanentLockout());
+ }
}
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
new file mode 100644
index 0000000..fa23d9a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import android.app.JobParametersProto;
+import android.net.NetworkCapabilitiesProto;
+import android.net.NetworkRequestProto;
+import com.android.server.job.ConstantsProto;
+import com.android.server.job.DataSetProto;
+import com.android.server.job.JobPackageHistoryProto;
+import com.android.server.job.JobPackageTrackerDumpProto;
+import com.android.server.job.JobSchedulerServiceDumpProto;
+import com.android.server.job.JobStatusDumpProto;
+import com.android.server.job.JobStatusShortInfoProto;
+import com.android.server.job.StateControllerProto;
+
+/** Test to check that the jobscheduler service properly outputs its dump state. */
+public class JobSchedulerIncidentTest extends ProtoDumpTestCase {
+ public void testJobSchedulerServiceDump() throws Exception {
+ final JobSchedulerServiceDumpProto dump =
+ getDump(JobSchedulerServiceDumpProto.parser(), "dumpsys jobscheduler --proto");
+
+ testConstantsProto(dump.getSettings());
+
+ for (int u : dump.getStartedUsersList()) {
+ assertTrue(0 <= u);
+ }
+
+ for (JobSchedulerServiceDumpProto.RegisteredJob rj : dump.getRegisteredJobsList()) {
+ testJobStatusShortInfoProto(rj.getInfo());
+ testJobStatusDumpProto(rj.getDump());
+ }
+
+ for (StateControllerProto c : dump.getControllersList()) {
+ testStateControllerProto(c);
+ }
+
+ for (JobSchedulerServiceDumpProto.PriorityOverride po : dump.getPriorityOverridesList()) {
+ assertTrue(0 <= po.getUid());
+ }
+
+ for (int buu : dump.getBackingUpUidsList()) {
+ assertTrue(0 <= buu);
+ }
+
+ testJobPackageHistoryProto(dump.getHistory());
+
+ testJobPackageTrackerDumpProto(dump.getPackageTracker());
+
+ for (JobSchedulerServiceDumpProto.PendingJob pj : dump.getPendingJobsList()) {
+ testJobStatusShortInfoProto(pj.getInfo());
+ testJobStatusDumpProto(pj.getDump());
+ assertTrue(0 <= pj.getEnqueuedDurationMs());
+ }
+
+ for (JobSchedulerServiceDumpProto.ActiveJob aj : dump.getActiveJobsList()) {
+ JobSchedulerServiceDumpProto.ActiveJob.InactiveJob ajIj = aj.getInactive();
+ assertTrue(0 <= ajIj.getTimeSinceStoppedMs());
+
+ JobSchedulerServiceDumpProto.ActiveJob.RunningJob ajRj = aj.getRunning();
+ testJobStatusShortInfoProto(ajRj.getInfo());
+ assertTrue(0 <= ajRj.getRunningDurationMs());
+ assertTrue(0 <= ajRj.getTimeUntilTimeoutMs());
+ testJobStatusDumpProto(ajRj.getDump());
+ assertTrue(0 <= ajRj.getTimeSinceMadeActiveMs());
+ assertTrue(0 <= ajRj.getPendingDurationMs());
+ }
+
+ assertTrue(0 <= dump.getMaxActiveJobs());
+ }
+
+ private void testConstantsProto(ConstantsProto c) throws Exception {
+ assertNotNull(c);
+
+ assertTrue(0 <= c.getMinIdleCount());
+ assertTrue(0 <= c.getMinChargingCount());
+ assertTrue(0 <= c.getMinBatteryNotLowCount());
+ assertTrue(0 <= c.getMinStorageNotLowCount());
+ assertTrue(0 <= c.getMinConnectivityCount());
+ assertTrue(0 <= c.getMinContentCount());
+ assertTrue(0 <= c.getMinReadyJobsCount());
+ assertTrue(0 <= c.getHeavyUseFactor());
+ assertTrue(0 <= c.getModerateUseFactor());
+ assertTrue(0 <= c.getFgJobCount());
+ assertTrue(0 <= c.getBgNormalJobCount());
+ assertTrue(0 <= c.getBgModerateJobCount());
+ assertTrue(0 <= c.getBgLowJobCount());
+ assertTrue(0 <= c.getBgCriticalJobCount());
+ assertTrue(0 <= c.getMaxStandardRescheduleCount());
+ assertTrue(0 <= c.getMaxWorkRescheduleCount());
+ assertTrue(0 <= c.getMinLinearBackoffTimeMs());
+ assertTrue(0 <= c.getMinExpBackoffTimeMs());
+ assertTrue(0 <= c.getStandbyHeartbeatTimeMs());
+ for (int sb : c.getStandbyBeatsList()) {
+ assertTrue(0 <= sb);
+ }
+ }
+
+ private void testDataSetProto(DataSetProto ds) throws Exception {
+ assertNotNull(ds);
+
+ assertTrue(0 <= ds.getStartClockTimeMs());
+ assertTrue(0 <= ds.getElapsedTimeMs());
+ assertTrue(0 <= ds.getPeriodMs());
+
+ for (DataSetProto.PackageEntryProto pe : ds.getPackageEntriesList()) {
+ assertTrue(0 <= pe.getUid());
+
+ assertTrue(0 <= pe.getPendingState().getDurationMs());
+ assertTrue(0 <= pe.getPendingState().getCount());
+ assertTrue(0 <= pe.getActiveState().getDurationMs());
+ assertTrue(0 <= pe.getActiveState().getCount());
+ assertTrue(0 <= pe.getActiveTopState().getDurationMs());
+ assertTrue(0 <= pe.getActiveTopState().getCount());
+
+ for (DataSetProto.PackageEntryProto.StopReasonCount src : pe.getStopReasonsList()) {
+ assertTrue(JobParametersProto.CancelReason.getDescriptor().getValues()
+ .contains(src.getReason().getValueDescriptor()));
+ assertTrue(0 <= src.getCount());
+ }
+ }
+ assertTrue(0 <= ds.getMaxConcurrency());
+ assertTrue(0 <= ds.getMaxForegroundConcurrency());
+ }
+
+ private void testJobPackageHistoryProto(JobPackageHistoryProto jph) throws Exception {
+ assertNotNull(jph);
+
+ for (JobPackageHistoryProto.HistoryEvent he : jph.getHistoryEventList()) {
+ assertTrue(JobPackageHistoryProto.Event.getDescriptor().getValues()
+ .contains(he.getEvent().getValueDescriptor()));
+ assertTrue(0 <= he.getTimeSinceEventMs()); // Should be positive.
+ assertTrue(0 <= he.getUid());
+ assertTrue(JobParametersProto.CancelReason.getDescriptor().getValues()
+ .contains(he.getStopReason().getValueDescriptor()));
+ }
+ }
+
+ private void testJobPackageTrackerDumpProto(JobPackageTrackerDumpProto jptd) throws Exception {
+ assertNotNull(jptd);
+
+ for (DataSetProto ds : jptd.getHistoricalStatsList()) {
+ testDataSetProto(ds);
+ }
+ testDataSetProto(jptd.getCurrentStats());
+ }
+
+ private void testJobStatusShortInfoProto(JobStatusShortInfoProto jssi) throws Exception {
+ assertNotNull(jssi);
+
+ assertTrue(0 <= jssi.getCallingUid());
+ }
+
+ private void testJobStatusDumpProto(JobStatusDumpProto jsd) throws Exception {
+ assertNotNull(jsd);
+
+ assertTrue(0 <= jsd.getCallingUid());
+ assertTrue(0 <= jsd.getSourceUid());
+ assertTrue(0 <= jsd.getSourceUserId());
+
+ JobStatusDumpProto.JobInfo ji = jsd.getJobInfo();
+ if (ji.getIsPeriodic()) {
+ assertTrue(0 <= ji.getPeriodIntervalMs());
+ assertTrue(0 <= ji.getPeriodFlexMs());
+ }
+ assertTrue(0 <= ji.getTriggerContentUpdateDelayMs());
+ assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
+ testNetworkRequestProto(ji.getRequiredNetwork());
+ assertTrue(0 <= ji.getTotalNetworkBytes());
+ assertTrue(0 <= ji.getMinLatencyMs());
+ assertTrue(0 <= ji.getMaxExecutionDelayMs());
+ JobStatusDumpProto.JobInfo.Backoff bp = ji.getBackoffPolicy();
+ assertTrue(JobStatusDumpProto.JobInfo.Backoff.Policy.getDescriptor().getValues()
+ .contains(bp.getPolicy().getValueDescriptor()));
+ assertTrue(0 <= bp.getInitialBackoffMs());
+
+ for (JobStatusDumpProto.Constraint c : jsd.getRequiredConstraintsList()) {
+ assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+ .contains(c.getValueDescriptor()));
+ }
+ for (JobStatusDumpProto.Constraint c : jsd.getSatisfiedConstraintsList()) {
+ assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+ .contains(c.getValueDescriptor()));
+ }
+ for (JobStatusDumpProto.Constraint c : jsd.getUnsatisfiedConstraintsList()) {
+ assertTrue(JobStatusDumpProto.Constraint.getDescriptor().getValues()
+ .contains(c.getValueDescriptor()));
+ }
+
+ for (JobStatusDumpProto.TrackingController tc : jsd.getTrackingControllersList()) {
+ assertTrue(JobStatusDumpProto.TrackingController.getDescriptor().getValues()
+ .contains(tc.getValueDescriptor()));
+ }
+
+ for (JobStatusDumpProto.JobWorkItem jwi : jsd.getPendingWorkList()) {
+ assertTrue(0 <= jwi.getDeliveryCount());
+ }
+ for (JobStatusDumpProto.JobWorkItem jwi : jsd.getExecutingWorkList()) {
+ assertTrue(0 <= jwi.getDeliveryCount());
+ }
+
+ assertTrue(JobStatusDumpProto.Bucket.getDescriptor().getValues()
+ .contains(jsd.getStandbyBucket().getValueDescriptor()));
+
+ assertTrue(0 <= jsd.getEnqueueDurationMs());
+
+ assertTrue(0 <= jsd.getNumFailures());
+
+ assertTrue(0 <= jsd.getLastSuccessfulRunTime());
+ assertTrue(0 <= jsd.getLastFailedRunTime());
+ }
+
+ private void testNetworkRequestProto(NetworkRequestProto nr) throws Exception {
+ assertNotNull(nr);
+
+ assertTrue(NetworkRequestProto.Type.getDescriptor().getValues()
+ .contains(nr.getType().getValueDescriptor()));
+ testNetworkCapabilitesProto(nr.getNetworkCapabilities());
+ }
+
+ private void testNetworkCapabilitesProto(NetworkCapabilitiesProto nc) throws Exception {
+ assertNotNull(nc);
+
+ for (NetworkCapabilitiesProto.Transport t : nc.getTransportsList()) {
+ assertTrue(NetworkCapabilitiesProto.Transport.getDescriptor().getValues()
+ .contains(t.getValueDescriptor()));
+ }
+ for (NetworkCapabilitiesProto.NetCapability c : nc.getCapabilitiesList()) {
+ assertTrue(NetworkCapabilitiesProto.NetCapability.getDescriptor().getValues()
+ .contains(c.getValueDescriptor()));
+ }
+
+ assertTrue(0 <= nc.getLinkUpBandwidthKbps());
+ assertTrue(0 <= nc.getLinkDownBandwidthKbps());
+ }
+
+ private void testStateControllerProto(StateControllerProto sc) throws Exception {
+ assertNotNull(sc);
+
+ StateControllerProto.AppIdleController aic = sc.getAppIdle();
+ for (StateControllerProto.AppIdleController.TrackedJob tj : aic.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ StateControllerProto.BackgroundJobsController bjc = sc.getBackground();
+ for (StateControllerProto.BackgroundJobsController.TrackedJob tj : bjc.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ StateControllerProto.BatteryController bc = sc.getBattery();
+ for (StateControllerProto.BatteryController.TrackedJob tj : bc.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ StateControllerProto.ConnectivityController cc = sc.getConnectivity();
+ for (StateControllerProto.ConnectivityController.TrackedJob tj : cc.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ testNetworkRequestProto(tj.getRequiredNetwork());
+ }
+ StateControllerProto.ContentObserverController coc = sc.getContentObserver();
+ for (StateControllerProto.ContentObserverController.TrackedJob tj : coc.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ for (StateControllerProto.ContentObserverController.Observer o : coc.getObserversList()) {
+ assertTrue(0 <= o.getUserId());
+
+ for (StateControllerProto.ContentObserverController.Observer.TriggerContentData tcd : o.getTriggersList()) {
+ for (StateControllerProto.ContentObserverController.Observer.TriggerContentData.JobInstance ji : tcd.getJobsList()) {
+ testJobStatusShortInfoProto(ji.getInfo());
+
+ assertTrue(0 <= ji.getSourceUid());
+ assertTrue(0 <= ji.getTriggerContentUpdateDelayMs());
+ assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
+ }
+ }
+ }
+ StateControllerProto.DeviceIdleJobsController dijc = sc.getDeviceIdle();
+ for (StateControllerProto.DeviceIdleJobsController.TrackedJob tj : dijc.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ StateControllerProto.IdleController ic = sc.getIdle();
+ for (StateControllerProto.IdleController.TrackedJob tj : ic.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ StateControllerProto.StorageController scr = sc.getStorage();
+ for (StateControllerProto.StorageController.TrackedJob tj : scr.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ StateControllerProto.TimeController tc = sc.getTime();
+ assertTrue(0 <= tc.getNowElapsedRealtime());
+ assertTrue(0 <= tc.getTimeUntilNextDelayAlarmMs());
+ assertTrue(0 <= tc.getTimeUntilNextDeadlineAlarmMs());
+ for (StateControllerProto.TimeController.TrackedJob tj : tc.getTrackedJobsList()) {
+ testJobStatusShortInfoProto(tj.getInfo());
+ assertTrue(0 <= tj.getSourceUid());
+ }
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java
new file mode 100644
index 0000000..69fe2ac
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import com.android.server.am.proto.MemInfoProto;
+import com.android.server.am.proto.MemInfoProto.AppData;
+import com.android.server.am.proto.MemInfoProto.MemItem;
+import com.android.server.am.proto.MemInfoProto.ProcessMemory;
+
+/** Test to check that ActivityManager properly outputs meminfo data. */
+public class MemInfoIncidentTest extends ProtoDumpTestCase {
+
+ public void testBatteryServiceDump() throws Exception {
+ final MemInfoProto dump =
+ getDump(MemInfoProto.parser(), "dumpsys meminfo -a --proto");
+
+ assertTrue(dump.getUptimeDurationMs() > 0);
+ assertTrue(dump.getElapsedRealtimeMs() >= 0);
+
+ for (ProcessMemory pm : dump.getNativeProcessesList()) {
+ testProcessMemory(pm);
+ }
+
+ for (AppData ad : dump.getAppProcessesList()) {
+ testAppData(ad);
+ }
+
+ for (MemItem mi : dump.getTotalPssByProcessList()) {
+ testMemItem(mi);
+ }
+ for (MemItem mi : dump.getTotalPssByOomAdjustmentList()) {
+ testMemItem(mi);
+ }
+ for (MemItem mi : dump.getTotalPssByCategoryList()) {
+ testMemItem(mi);
+ }
+
+ assertTrue(0 <= dump.getTotalRamKb());
+ assertTrue(0 <= dump.getCachedPssKb());
+ assertTrue(0 <= dump.getCachedKernelKb());
+ assertTrue(0 <= dump.getFreeKb());
+ assertTrue(0 <= dump.getUsedPssKb());
+ assertTrue(0 <= dump.getUsedKernelKb());
+
+ assertTrue(0 <= dump.getLostRamKb());
+
+ assertTrue(0 <= dump.getTotalZramKb());
+ assertTrue(0 <= dump.getZramPhysicalUsedInSwapKb());
+ assertTrue(0 <= dump.getTotalZramSwapKb());
+
+ assertTrue(0 <= dump.getKsmSharingKb());
+ assertTrue(0 <= dump.getKsmSharedKb());
+ assertTrue(0 <= dump.getKsmUnsharedKb());
+ assertTrue(0 <= dump.getKsmVolatileKb());
+
+ assertTrue(0 < dump.getTuningMb());
+ assertTrue(0 < dump.getTuningLargeMb());
+
+ assertTrue(0 <= dump.getOomKb());
+
+ assertTrue(0 < dump.getRestoreLimitKb());
+ }
+
+ private void testProcessMemory(ProcessMemory pm) throws Exception {
+ assertNotNull(pm);
+
+ assertTrue(0 < pm.getPid());
+ // On most Linux machines, the max pid value is 32768 (=2^15), but, it can be set to any
+ // value up to 4194304 (=2^22) if necessary.
+ assertTrue(4194304 >= pm.getPid());
+
+ testHeapInfo(pm.getNativeHeap());
+ testHeapInfo(pm.getDalvikHeap());
+
+ for (ProcessMemory.MemoryInfo mi : pm.getOtherHeapsList()) {
+ testMemoryInfo(mi);
+ }
+ testMemoryInfo(pm.getUnknownHeap());
+ testHeapInfo(pm.getTotalHeap());
+
+ for (ProcessMemory.MemoryInfo mi : pm.getDalvikDetailsList()) {
+ testMemoryInfo(mi);
+ }
+
+ ProcessMemory.AppSummary as = pm.getAppSummary();
+ assertTrue(0 <= as.getJavaHeapPssKb());
+ assertTrue(0 <= as.getNativeHeapPssKb());
+ assertTrue(0 <= as.getCodePssKb());
+ assertTrue(0 <= as.getStackPssKb());
+ assertTrue(0 <= as.getGraphicsPssKb());
+ assertTrue(0 <= as.getPrivateOtherPssKb());
+ assertTrue(0 <= as.getSystemPssKb());
+ assertTrue(0 <= as.getTotalSwapPss());
+ assertTrue(0 <= as.getTotalSwapKb());
+ }
+
+ private void testMemoryInfo(ProcessMemory.MemoryInfo mi) throws Exception {
+ assertNotNull(mi);
+
+ assertTrue(0 <= mi.getTotalPssKb());
+ assertTrue(0 <= mi.getCleanPssKb());
+ assertTrue(0 <= mi.getSharedDirtyKb());
+ assertTrue(0 <= mi.getPrivateDirtyKb());
+ assertTrue(0 <= mi.getSharedCleanKb());
+ assertTrue(0 <= mi.getPrivateCleanKb());
+ assertTrue(0 <= mi.getDirtySwapKb());
+ assertTrue(0 <= mi.getDirtySwapPssKb());
+ }
+
+ private void testHeapInfo(ProcessMemory.HeapInfo hi) throws Exception {
+ assertNotNull(hi);
+
+ testMemoryInfo(hi.getMemInfo());
+ assertTrue(0 <= hi.getHeapSizeKb());
+ assertTrue(0 <= hi.getHeapAllocKb());
+ assertTrue(0 <= hi.getHeapFreeKb());
+ }
+
+ private void testAppData(AppData ad) throws Exception {
+ assertNotNull(ad);
+
+ testProcessMemory(ad.getProcessMemory());
+
+ AppData.ObjectStats os = ad.getObjects();
+ assertTrue(0 <= os.getViewInstanceCount());
+ assertTrue(0 <= os.getViewRootInstanceCount());
+ assertTrue(0 <= os.getAppContextInstanceCount());
+ assertTrue(0 <= os.getActivityInstanceCount());
+ assertTrue(0 <= os.getGlobalAssetCount());
+ assertTrue(0 <= os.getGlobalAssetManagerCount());
+ assertTrue(0 <= os.getLocalBinderObjectCount());
+ assertTrue(0 <= os.getProxyBinderObjectCount());
+ assertTrue(0 <= os.getParcelMemoryKb());
+ assertTrue(0 <= os.getParcelCount());
+ assertTrue(0 <= os.getBinderObjectDeathCount());
+ assertTrue(0 <= os.getOpenSslSocketCount());
+ assertTrue(0 <= os.getWebviewInstanceCount());
+
+ AppData.SqlStats ss = ad.getSql();
+ assertTrue(0 <= ss.getMemoryUsedKb());
+ assertTrue(0 <= ss.getPagecacheOverflowKb());
+ assertTrue(0 <= ss.getMallocSizeKb());
+ for (AppData.SqlStats.Database d : ss.getDatabasesList()) {
+ assertTrue(0 <= d.getPageSize());
+ assertTrue(0 <= d.getDbSize());
+ assertTrue(0 <= d.getLookasideB());
+ }
+ }
+
+ private void testMemItem(MemItem mi) throws Exception {
+ assertNotNull(mi);
+
+ assertTrue(0 <= mi.getPssKb());
+ assertTrue(0 <= mi.getSwapPssKb());
+
+ for (MemItem smi : mi.getSubItemsList()) {
+ testMemItem(smi);
+ }
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
index f5c13c2..bdf7dcc 100644
--- a/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/NetstatsIncidentTest.java
@@ -374,16 +374,14 @@
assertNotNegative("TX bytes", bucket.getTxBytes());
assertNotNegative("TX packets", bucket.getTxPackets());
-// 10 was still too big? // It should be safe to say # of bytes >= 10 * 10 of packets, due to headers, etc...
- final long FACTOR = 4;
assertTrue(
String.format("# of bytes %d too small for # of packets %d",
bucket.getRxBytes(), bucket.getRxPackets()),
- bucket.getRxBytes() >= bucket.getRxPackets() * FACTOR);
+ bucket.getRxBytes() >= bucket.getRxPackets());
assertTrue(
String.format("# of bytes %d too small for # of packets %d",
bucket.getTxBytes(), bucket.getTxPackets()),
- bucket.getTxBytes() >= bucket.getTxPackets() * FACTOR);
+ bucket.getTxBytes() >= bucket.getTxPackets());
}
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java
new file mode 100644
index 0000000..a96be46
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationServiceDumpProto;
+import android.service.notification.NotificationRecordProto.State;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
+import android.service.notification.ZenModeProto;
+import android.service.notification.ZenModeProto.ZenMode;
+
+/**
+ * Test to check that the notification service properly outputs its dump state.
+ *
+ * make -j32 CtsIncidentHostTestCases
+ * cts-tradefed run singleCommand cts-dev -d --module CtsIncidentHostTestCases
+ */
+public class NotificationIncidentTest extends ProtoDumpTestCase {
+ // Constants from android.app.NotificationManager
+ private static final int IMPORTANCE_UNSPECIFIED = -1000;
+ private static final int IMPORTANCE_NONE = 0;
+ private static final int IMPORTANCE_MAX = 5;
+ private static final int VISIBILITY_NO_OVERRIDE = -1000;
+ // Constants from android.app.Notification
+ private static final int PRIORITY_MIN = -2;
+ private static final int PRIORITY_MAX = 2;
+ private static final int VISIBILITY_SECRET = -1;
+ private static final int VISIBILITY_PUBLIC = 1;
+ // These constants are those in PackageManager.
+ public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+ /**
+ * Tests that at least one notification is posted, and verify its properties are plausible.
+ */
+ public void testNotificationRecords() throws Exception {
+ final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+ "dumpsys notification --proto");
+
+ assertTrue(dump.getRecordsCount() > 0);
+ boolean found = false;
+ for (NotificationRecordProto record : dump.getRecordsList()) {
+ if (record.getKey().contains("android")) {
+ found = true;
+ assertEquals(State.POSTED, record.getState());
+ assertTrue(record.getImportance() > IMPORTANCE_NONE);
+
+ // Ensure these fields exist, at least
+ record.getFlags();
+ record.getChannelId();
+ record.getSound();
+ record.getSoundUsage();
+ record.getCanVibrate();
+ record.getCanShowLight();
+ record.getGroupKey();
+ }
+ assertTrue(State.SNOOZED != record.getState());
+ }
+
+ assertTrue(found);
+ }
+
+ /** Test valid values from the RankingHelper. */
+ public void testRankingConfig() throws Exception {
+ final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+ "dumpsys notification --proto");
+
+ RankingHelperProto rhProto = dump.getRankingConfig();
+ for (RecordProto rp : rhProto.getRecordsList()) {
+ verifyRecordProto(rp);
+ }
+ for (RecordProto rp : rhProto.getRecordsRestoredWithoutUidList()) {
+ verifyRecordProto(rp);
+ }
+ }
+
+ private void verifyRecordProto(RecordProto rp) throws Exception {
+ assertTrue(!rp.getPackage().isEmpty());
+ assertTrue(rp.getUid() == -10000 || rp.getUid() >= 0);
+ assertTrue(rp.getImportance() == IMPORTANCE_UNSPECIFIED ||
+ (rp.getImportance() >= IMPORTANCE_NONE && rp.getImportance() <= IMPORTANCE_MAX));
+ assertTrue(rp.getPriority() >= PRIORITY_MIN && rp.getPriority() <= PRIORITY_MAX);
+ assertTrue(rp.getVisibility() == VISIBILITY_NO_OVERRIDE ||
+ (rp.getVisibility() >= VISIBILITY_SECRET &&
+ rp.getVisibility() <= VISIBILITY_PUBLIC));
+ }
+
+ // Tests default state: zen mode off, no suppressors
+ public void testZenMode() throws Exception {
+ final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+ "dumpsys notification --proto");
+ ZenModeProto zenProto = dump.getZen();
+
+ assertEquals(ZenMode.ZEN_MODE_OFF, zenProto.getZenMode());
+ assertEquals(0, zenProto.getEnabledActiveConditionsCount());
+
+ // b/64606626 Watches intentionally suppress notifications always
+ if (!getDevice().hasFeature(FEATURE_WATCH)) {
+ assertEquals(0, zenProto.getSuppressedEffects());
+ assertEquals(0, zenProto.getSuppressorsCount());
+ }
+
+ zenProto.getPolicy();
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java b/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
deleted file mode 100644
index f91c8a7..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts;
-
-import android.service.notification.NotificationRecordProto;
-import android.service.notification.NotificationServiceDumpProto;
-import android.service.notification.State;
-import android.service.notification.ZenMode;
-import android.service.notification.ZenModeProto;
-
-/**
- * Test to check that the notification service properly outputs its dump state.
- *
- * make -j32 CtsIncidentHostTestCases
- * cts-tradefed run singleCommand cts-dev -d --module CtsIncidentHostTestCases
- */
-public class NotificationTest extends ProtoDumpTestCase {
- // These constants are those in PackageManager.
- public static final String FEATURE_WATCH = "android.hardware.type.watch";
-
- /**
- * Tests that at least one notification is posted, and verify its properties are plausible.
- */
- public void testNotificationRecords() throws Exception {
- final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
- "dumpsys notification --proto");
-
- assertTrue(dump.getRecordsCount() > 0);
- boolean found = false;
- for (NotificationRecordProto record : dump.getRecordsList()) {
- if (record.getKey().contains("android")) {
- found = true;
- assertEquals(State.POSTED, record.getState());
- assertTrue(record.getImportance() > 0 /* NotificationManager.IMPORTANCE_NONE */);
-
- // Ensure these fields exist, at least
- record.getFlags();
- record.getChannelId();
- record.getSound();
- record.getSoundUsage();
- record.getCanVibrate();
- record.getCanShowLight();
- record.getGroupKey();
- }
- assertTrue(State.SNOOZED != record.getState());
- }
-
- assertTrue(found);
- }
-
- // Tests default state: zen mode off, no suppressors
- public void testZenMode() throws Exception {
- final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
- "dumpsys notification --proto");
- ZenModeProto zenProto = dump.getZen();
-
- assertEquals(ZenMode.ZEN_MODE_OFF, zenProto.getZenMode());
- assertEquals(0, zenProto.getEnabledActiveConditionsCount());
-
- // b/64606626 Watches intentionally suppress notifications always
- if (!getDevice().hasFeature(FEATURE_WATCH)) {
- assertEquals(0, zenProto.getSuppressedEffects());
- assertEquals(0, zenProto.getSuppressorsCount());
- }
-
- zenProto.getPolicy();
- }
-}
diff --git a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
index 77c0163..a0cfd07 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
@@ -16,28 +16,37 @@
package com.android.server.cts;
+import android.app.ProcessState;
+import android.content.IntentProto;
+import android.os.BatteryManagerProto;
import android.os.LooperProto;
-import android.service.power.PowerServiceDumpProto;
-import android.service.power.PowerServiceSettingsAndConfigurationDumpProto;
+import android.os.PowerManagerInternalProto;
+import android.os.PowerManagerProto;
+import com.android.server.power.PowerManagerServiceDumpProto;
+import com.android.server.power.PowerServiceSettingsAndConfigurationDumpProto;
+import com.android.server.power.WakeLockProto;
/** Test to check that the power manager properly outputs its dump state. */
public class PowerIncidentTest extends ProtoDumpTestCase {
private static final int SYSTEM_UID = 1000;
public void testPowerServiceDump() throws Exception {
- final PowerServiceDumpProto dump =
- getDump(PowerServiceDumpProto.parser(), "dumpsys power --proto");
+ final PowerManagerServiceDumpProto dump =
+ getDump(PowerManagerServiceDumpProto.parser(), "dumpsys power --proto");
+
+ assertTrue(dump.getBatteryLevel() >= 0);
+ assertTrue(dump.getBatteryLevel() <= 100);
assertTrue(
- PowerServiceDumpProto.Wakefulness.getDescriptor()
+ PowerManagerInternalProto.Wakefulness.getDescriptor()
.getValues()
.contains(dump.getWakefulness().getValueDescriptor()));
assertTrue(
- PowerServiceDumpProto.PlugType.getDescriptor()
+ BatteryManagerProto.PlugType.getDescriptor()
.getValues()
.contains(dump.getPlugType().getValueDescriptor()));
assertTrue(
- PowerServiceDumpProto.DockState.getDescriptor()
+ IntentProto.DockState.getDescriptor()
.getValues()
.contains(dump.getDockState().getValueDescriptor()));
@@ -47,22 +56,34 @@
assertTrue(settingsAndConfiguration.getMaximumScreenDimDurationConfigMs() >= 0);
assertTrue(settingsAndConfiguration.getMaximumScreenDimRatioConfig() > 0);
assertTrue(settingsAndConfiguration.getScreenOffTimeoutSettingMs() > 0);
+ // Default value is -1.
+ assertTrue(settingsAndConfiguration.getSleepTimeoutSettingMs() >= -1);
assertTrue(settingsAndConfiguration.getMaximumScreenOffTimeoutFromDeviceAdminMs() > 0);
+ // -1 is used to disable, so is valid.
+ assertTrue(settingsAndConfiguration.getUserActivityTimeoutOverrideFromWindowManagerMs() >= -1);
final PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
brightnessLimits = settingsAndConfiguration.getScreenBrightnessSettingLimits();
- assertTrue(brightnessLimits.getSettingMaximum() > 0);
+ int settingMax = brightnessLimits.getSettingMaximum();
+ int settingMin = brightnessLimits.getSettingMinimum();
+ assertTrue(settingMin >= 0);
+ assertTrue(settingMax > 0);
+ assertTrue(settingMax >= settingMin);
assertTrue(brightnessLimits.getSettingDefault() > 0);
assertTrue(brightnessLimits.getSettingForVrDefault() > 0);
- final PowerServiceDumpProto.UidProto uid = dump.getUids(0);
+ final PowerManagerServiceDumpProto.UidStateProto uid = dump.getUidStates(0);
assertEquals(uid.getUid(), SYSTEM_UID);
assertEquals(uid.getUidString(), Integer.toString(SYSTEM_UID));
assertTrue(uid.getIsActive());
assertFalse(uid.getIsProcessStateUnknown());
- assertTrue(
- PowerServiceDumpProto.UidProto.ProcessState.getDescriptor()
- .getValues()
- .contains(uid.getProcessState().getValueDescriptor()));
+
+ for (PowerManagerServiceDumpProto.UidStateProto us : dump.getUidStatesList()) {
+ assertTrue(0 <= us.getUid());
+ assertTrue(0 <= us.getNumWakeLocks());
+ assertTrue(ProcessState.getDescriptor()
+ .getValues()
+ .contains(us.getProcessState().getValueDescriptor()));
+ }
final LooperProto looper = dump.getLooper();
assertNotNull(looper.getThreadName());
@@ -70,5 +91,26 @@
assertTrue(looper.getIdentityHashCode() > 0);
assertTrue(dump.getSuspendBlockersCount() > 0);
+
+ // Check that times/durations are not incorrectly negative.
+ assertTrue(dump.getNotifyLongScheduledMs() >= 0);
+ assertTrue(dump.getNotifyLongDispatchedMs() >= 0);
+ assertTrue(dump.getNotifyLongNextCheckMs() >= 0);
+ assertTrue(dump.getLastWakeTimeMs() >= 0);
+ assertTrue(dump.getLastSleepTimeMs() >= 0);
+ assertTrue(dump.getLastUserActivityTimeMs() >= 0);
+ assertTrue(dump.getLastUserActivityTimeNoChangeLightsMs() >= 0);
+ assertTrue(dump.getLastInteractivePowerHintTimeMs() >= 0);
+ assertTrue(dump.getLastScreenBrightnessBoostTimeMs() >= 0);
+ // -1 is a valid value.
+ assertTrue(dump.getSleepTimeoutMs() >= -1);
+ assertTrue(dump.getScreenOffTimeoutMs() >= 0);
+ assertTrue(dump.getScreenDimDurationMs() >= 0);
+
+ for (WakeLockProto wl : dump.getWakeLocksList()) {
+ assertTrue(0 <= wl.getAcqMs());
+ assertTrue(0 <= wl.getUid());
+ assertTrue(0 <= wl.getPid());
+ }
}
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/PrintProtoTest.java b/hostsidetests/incident/src/com/android/server/cts/PrintProtoTest.java
new file mode 100644
index 0000000..24f3d2a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/PrintProtoTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import android.service.print.PrintServiceDumpProto;
+import android.service.print.PrintSpoolerStateProto;
+import android.service.print.PrintUserStateProto;
+
+import com.android.tradefed.log.LogUtil;
+
+/**
+ * Test proto dump of print
+ */
+public class PrintProtoTest extends ProtoDumpTestCase {
+ /**
+ * Test that print dump is reasonable
+ *
+ * @throws Exception
+ */
+ public void testDump() throws Exception {
+ // If the device doesn't support printing, then pass.
+ if (!getDevice().hasFeature("android.software.print")) {
+ LogUtil.CLog.d("Bypass as android.software.print is not supported.");
+ return;
+ }
+
+ PrintServiceDumpProto dump = getDump(PrintServiceDumpProto.parser(),
+ "dumpsys print --proto");
+
+ assertTrue(dump.getUserStatesCount() > 0);
+
+ PrintUserStateProto userState = dump.getUserStatesList().get(0);
+ assertEquals(0, userState.getUserId());
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProcStatsProtoTest.java b/hostsidetests/incident/src/com/android/server/cts/ProcStatsProtoTest.java
new file mode 100644
index 0000000..640da22
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/ProcStatsProtoTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts;
+
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
+
+/**
+ * Test proto dump of procstats
+ *
+ * $ make -j32 CtsIncidentHostTestCases
+ * $ cts-tradefed run cts-dev -m CtsIncidentHostTestCases \
+ * -t com.android.server.cts.ProcStatsProtoTest
+ */
+public class ProcStatsProtoTest extends ProtoDumpTestCase {
+
+ private static final String DEVICE_SIDE_TEST_APK = "CtsProcStatsProtoApp.apk";
+ private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.procstats";
+ private static final String TEST_APP_TAG = "ProcstatsAppRunningTest";
+ private static final String TEST_APP_LOG = "Procstats app is running";
+ private static final int WAIT_MS = 1000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getDevice().executeShellCommand("dumpsys procstats --clear");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ super.tearDown();
+ }
+
+ /**
+ * Test that procstats dump is reasonable, it installs procstats app and
+ * starts the activity, then asserts the procstats dump contains the package.
+ *
+ * @throws Exception
+ */
+ public void testDump() throws Exception {
+ installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
+ int retries = 3;
+ do {
+ getDevice().executeShellCommand(
+ "am start -n com.android.server.cts.procstats/.SimpleActivity");
+ } while (!checkLogcatForText(TEST_APP_TAG, TEST_APP_LOG, WAIT_MS) && retries-- > 0);
+
+ final ProcessStatsServiceDumpProto dump = getDump(ProcessStatsServiceDumpProto.parser(),
+ "dumpsys procstats --proto");
+
+ int N = dump.getProcstatsNow().getProcessStatsCount();
+ boolean containsTestApp = false;
+ for (int i = 0; i < N; i++) {
+ ProcessStatsProto ps = dump.getProcstatsNow().getProcessStats(i);
+ if (DEVICE_SIDE_TEST_PACKAGE.equals(ps.getProcess())) {
+ containsTestApp = true;
+ }
+ }
+
+ assertTrue(N > 0);
+ assertTrue(containsTestApp); // found test app in procstats dump
+ }
+}
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
index 5739bf4..d273033 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
@@ -17,6 +17,7 @@
package com.android.server.cts;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ddmlib.testrunner.TestResult;
@@ -31,11 +32,14 @@
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IBuildReceiver;
+import com.google.common.base.Charsets;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import com.google.protobuf.Parser;
import java.io.FileNotFoundException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -176,4 +180,66 @@
assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
return matcher.group(1);
}
+
+ /**
+ * Runs logcat and waits (for a maximumum of maxTimeMs) until the desired text is displayed with
+ * the given tag.
+ * Logcat is not cleared, so make sure that text is unique (won't get false hits from old data).
+ * Note that, in practice, the actual max wait time seems to be about 10s longer than maxTimeMs.
+ * Returns true means the desired log line is found.
+ */
+ protected boolean checkLogcatForText(String logcatTag, String text, int maxTimeMs) {
+ IShellOutputReceiver receiver = new IShellOutputReceiver() {
+ private final StringBuilder mOutputBuffer = new StringBuilder();
+ private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
+
+ @Override
+ public void addOutput(byte[] data, int offset, int length) {
+ if (!isCancelled()) {
+ synchronized (mOutputBuffer) {
+ String s = new String(data, offset, length, Charsets.UTF_8);
+ mOutputBuffer.append(s);
+ if (checkBufferForText()) {
+ mIsCanceled.set(true);
+ }
+ }
+ }
+ }
+
+ private boolean checkBufferForText() {
+ if (mOutputBuffer.indexOf(text) > -1) {
+ return true;
+ } else {
+ // delete all old data (except the last few chars) since they don't contain text
+ // (presumably large chunks of data will be added at a time, so this is
+ // sufficiently efficient.)
+ int newStart = mOutputBuffer.length() - text.length();
+ if (newStart > 0) {
+ mOutputBuffer.delete(0, newStart);
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mIsCanceled.get();
+ }
+
+ @Override
+ public void flush() {
+ }
+ };
+
+ try {
+ // Wait for at most maxTimeMs for logcat to display the desired text.
+ getDevice().executeShellCommand(String.format("logcat -s %s -e '%s'", logcatTag, text),
+ receiver, maxTimeMs, TimeUnit.MILLISECONDS, 0);
+ return receiver.isCancelled();
+ } catch (com.android.tradefed.device.DeviceNotAvailableException e) {
+ System.err.println(e);
+ }
+ return false;
+ }
+
}
diff --git a/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
index 7ea00e1..d9ee469 100644
--- a/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
@@ -58,7 +58,7 @@
private void verifySettings(GeneratedMessage settings) throws Exception {
verifySettings(getSettingProtos(settings));
- final List<SettingsOperationProto> ops = invoke(settings, "getHistoricalOpList");
+ final List<SettingsOperationProto> ops = invoke(settings, "getHistoricalOperationsList");
for (SettingsOperationProto op : ops) {
assertTrue(op.getTimestamp() >= 0);
assertNotNull(op.getOperation());
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
index 307693a..1f694d2 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
@@ -50,12 +50,6 @@
public static final String EXTRA_EVENT_SENDER = "event_sender";
/**
- * Intent extra key for Event parameters like
- * {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
- */
- public static final String EXTRA_EVENT_PARAMS = "event_params";
-
- /**
* Intent extra key for what type a device event is. Values are {@link DeviceEventType#name()}.
*
* @see android.content.Intent#putExtra(String,String)
@@ -73,29 +67,6 @@
public static final String EXTRA_EVENT_TIME = "event_time";
/**
- * Parameter for {@link DeviceEventType}.
- */
- public enum DeviceEventTypeParam {
-
- /**
- * Param for {@link DeviceEventType#ON_START_INPUT}. Represents if IME is restarting.
- */
- ON_START_INPUT_RESTARTING(DeviceEventType.ON_START_INPUT, "onStartInput.restarting");
-
- private final DeviceEventType mType;
- private final String mName;
-
- DeviceEventTypeParam(DeviceEventType type, String name) {
- mType = type;
- mName = name;
- }
-
- public String getName() {
- return mName;
- }
- }
-
- /**
* Types of device event, a value of {@link #EXTRA_EVENT_TYPE}.
*/
public enum DeviceEventType {
@@ -135,15 +106,5 @@
/** Test start and end event types. */
TEST_START,
TEST_END,
-
- /**
- * {@link android.view.inputmethod.InputMethod#showSoftInput}
- */
- SHOW_SOFT_INPUT,
-
- /**
- * {@link android.view.inputmethod.InputMethod#hideSoftInput}
- */
- HIDE_SOFT_INPUT,
}
}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
index 9288051..172e1a7 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/EventProviderConstants.java
@@ -58,9 +58,6 @@
// This is constants holding class, can't instantiate.
private EventTableConstants() {}
- /** Column name of the table that holds Event extras in json format. */
- public static final String EXTRAS = "extras";
-
/** Name of the table in content provider and database. */
public static final String NAME = "events";
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
index aa90e11..4502325 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
@@ -42,11 +42,4 @@
"android.inputmethodservice.cts.devicetest.InputMethodServiceDeviceTest";
public static final String TEST_CREATE_IME1 = "testCreateIme1";
public static final String TEST_SWITCH_IME1_TO_IME2 = "testSwitchIme1ToIme2";
- public static final String TEST_IME1_IS_NOT_CURRENT_IME = "testIme1IsNotCurrentIme";
- public static final String TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1
- = "testSearchView_giveFocusShowIme1";
- public static final String TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1
- = "testSearchView_setQueryHideIme1";
- public static final String TEST_ON_START_INPUT_CALLED_ONCE_IME1
- = "testOnStartInputCalledOnceIme1";
}
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
index b984aa1..643eb52 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
@@ -29,11 +29,6 @@
// Copied from android.content.pm.PackageManager#FEATURE_INPUT_METHODS.
public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
- /** Command to check whether system has specified {@code featureName} feature. */
- public static String hasFeature(final String featureName) {
- return "cmd package has-feature " + featureName;
- }
-
private static final String SETTING_DEFAULT_IME = "secure default_input_method";
/** Command to get ID of current IME. */
@@ -56,6 +51,11 @@
return "ime disable " + imeId;
}
+ /** Command to reset currently selected/enabled IMEs to the default ones. */
+ public static String resetImes() {
+ return "ime reset";
+ }
+
/** Command to delete all records of IME event provider. */
public static String deleteContent(final String contentUri) {
return "content delete --uri " + contentUri;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml
index f0efb94..94ba557 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/res/layout/activity_inputmethod_test.xml
@@ -25,14 +25,5 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"/>
- <SearchView
- android:id="@+id/search_view"
- android:layout_below="@id/text_entry"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:iconifiedByDefault="false"
- android:queryHint="hint"
- android:inputType="textCapCharacters"
- android:imeOptions="actionDone" />
</RelativeLayout>
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
index e5613ff..b1f16e5 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InputMethodServiceDeviceTest.java
@@ -16,19 +16,12 @@
package android.inputmethodservice.cts.devicetest;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
import static android.inputmethodservice.cts.DeviceEvent.isFrom;
import static android.inputmethodservice.cts.DeviceEvent.isNewerThan;
import static android.inputmethodservice.cts.DeviceEvent.isType;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.HIDE_SOFT_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
import static android.inputmethodservice.cts.common.ImeCommandConstants.ACTION_IME_COMMAND;
import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD;
import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_ARG_STRING1;
@@ -36,29 +29,24 @@
import static android.inputmethodservice.cts.devicetest.BusyWaitUtils.pollingCheck;
import static android.inputmethodservice.cts.devicetest.MoreCollectors.startingFrom;
-import android.app.Activity;
import android.inputmethodservice.cts.DeviceEvent;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
import android.inputmethodservice.cts.common.Ime1Constants;
import android.inputmethodservice.cts.common.Ime2Constants;
import android.inputmethodservice.cts.common.test.DeviceTestConstants;
import android.inputmethodservice.cts.common.test.ShellCommandUtils;
import android.inputmethodservice.cts.devicetest.SequenceMatcher.MatchResult;
import android.os.SystemClock;
-import android.widget.SearchView;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
-import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class InputMethodServiceDeviceTest {
@@ -72,8 +60,7 @@
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))),
TIMEOUT, "CtsInputMethod1.onCreate is called");
final long startActivityTime = SystemClock.uptimeMillis();
@@ -81,8 +68,7 @@
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(startActivityTime))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
}
@@ -94,8 +80,7 @@
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))),
TIMEOUT, "CtsInputMethod1.onCreate is called");
final long startActivityTime = SystemClock.uptimeMillis();
@@ -103,8 +88,7 @@
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(startActivityTime))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
helper.findUiObject(R.id.text_entry).click();
@@ -121,8 +105,7 @@
TIMEOUT, "CtsInputMethod2 is current IME");
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(switchImeTime))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_DESTROY))),
TIMEOUT, "CtsInputMethod1.onDestroy is called");
pollingCheck(() -> helper.queryAllEvents()
.filter(isNewerThan(switchImeTime))
@@ -133,97 +116,6 @@
"CtsInputMethod2.onCreate and onStartInput are called in sequence");
}
- /** Test to check CtsInputMethod1 isn't current IME. */
- @Test
- public void testIme1IsNotCurrentIme() throws Throwable {
- final TestHelper helper =
- new TestHelper(getClass(), DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
-
- helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
- helper.findUiObject(R.id.text_entry).click();
-
- pollingCheck(() -> !helper.shell(ShellCommandUtils.getCurrentIme())
- .equals(Ime1Constants.IME_ID),
- TIMEOUT,
- "CtsInputMethod1 is uninstalled or disabled, and current IME becomes other IME");
- }
-
- @Test
- public void testSearchView_giveFocusShowIme1() throws Throwable {
- final TestHelper helper = new TestHelper(
- getClass(), DeviceTestConstants.TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1);
-
- helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
- helper.findUiObject(R.id.search_view).click();
- pollingCheck(() -> helper.queryAllEvents()
- .collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(SHOW_SOFT_INPUT)))
- .findAny().isPresent(),
- TIMEOUT, "CtsInputMethod1.showSoftInput is called");
- pollingCheck(() -> helper.queryAllEvents()
- .collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
- .findAny().isPresent(),
- TIMEOUT, "CtsInputMethod1.onStartInput is called");
- }
-
- @Test
- public void testSearchView_setQueryHideIme1() throws Throwable {
- final TestHelper helper = new TestHelper(
- getClass(), DeviceTestConstants.TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1);
-
- final Activity activity = helper.launchActivitySync(
- DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
- final SearchView searchView = (SearchView) activity.findViewById(R.id.search_view);
- helper.findUiObject(R.id.search_view).click();
- // test SearchView.onSubmitQuery() closes IME. Alternatively, onCloseClicked() closes IME.
- // submits the query, should dismiss the inputMethod.
- activity.runOnUiThread(() -> searchView.setQuery("test", true /* submit */));
-
- pollingCheck(() -> helper.queryAllEvents()
- .collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_FINISH_INPUT)))
- .findAny().isPresent(),
- TIMEOUT, "CtsInputMethod1.onFinishInput is called");
- pollingCheck(() -> helper.queryAllEvents()
- .collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(HIDE_SOFT_INPUT)))
- .findAny().isPresent(),
- TIMEOUT, "CtsInputMethod1.hideSoftInput is called");
- }
-
- @Test
- public void testOnStartInputCalledOnceIme1() throws Exception {
- final TestHelper helper = new TestHelper(
- getClass(), DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
-
- helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
- helper.findUiObject(R.id.text_entry).click();
-
- // we should've only one onStartInput call.
- pollingCheck(() -> helper.queryAllEvents()
- .collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
- .findAny()
- .isPresent(),
- TIMEOUT, "CtsInputMethod1.onStartInput is called");
- List<DeviceEvent> startInputEvents = helper.queryAllEvents()
- .collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
- .collect(Collectors.toList());
-
- assertEquals("CtsInputMethod1.onStartInput is called exactly once",
- startInputEvents.size(),
- 1);
-
- // check if that single event didn't cause IME restart.
- final DeviceEvent event = startInputEvents.get(0);
- Boolean isRestarting = DeviceEvent.getEventParamBoolean(
- DeviceEventTypeParam.ON_START_INPUT_RESTARTING, event);
- assertTrue(isRestarting != null);
- assertFalse(isRestarting);
- }
-
/**
* Build stream collector of {@link DeviceEvent} collecting sequence that elements have
* specified types.
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
index a935f0b..59692a6 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/MoreCollectors.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.inputmethodservice.cts.devicetest;
import java.util.function.BiConsumer;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
index 36b711e..996dad9 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/SequenceMatcher.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.inputmethodservice.cts.devicetest;
import java.util.ArrayList;
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
index 7f6ba2e..a4b1aae 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/TestHelper.java
@@ -20,12 +20,9 @@
import static android.inputmethodservice.cts.DeviceEvent.isType;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
-import android.app.Instrumentation;
-import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.database.Cursor;
import android.inputmethodservice.cts.DeviceEvent;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
@@ -57,7 +54,6 @@
private final ContentResolver mResolver;
private final Context mTargetContext;
private final UiDevice mUiDevice;
- private final Instrumentation mInstrumentation;
/**
* Construct a helper object of specified test method.
@@ -70,8 +66,7 @@
mTestInfo = new TestInfo(testContext.getPackageName(), testClass.getName(), testMethod);
mResolver = testContext.getContentResolver();
mTargetContext = InstrumentationRegistry.getTargetContext();
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mUiDevice = UiDevice.getInstance(mInstrumentation);
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
/**
@@ -110,22 +105,6 @@
}
/**
- * Launch test activity synchronously.
- *
- * @param packageName activity's app package name.
- * @param className activity's class name.
- * @return instance of Activity
- */
- Activity launchActivitySync(final String packageName, final String className) {
- final Intent intent = new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClassName(packageName, className)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return mInstrumentation.startActivitySync(intent);
- }
-
- /**
* Return all device events as {@link Stream}
* @return {@link Stream<DeviceEvent>} of all device events.
*/
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
index 9fdcec6..f30a85b 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
+++ b/hostsidetests/inputmethodservice/deviceside/lib/Android.mk
@@ -19,7 +19,6 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-annotations \
- json \
CtsInputMethodServiceCommon
LOCAL_MODULE_TAGS := tests
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
index ff412ef..b6098f9 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
@@ -17,20 +17,17 @@
package android.inputmethodservice.cts;
import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_PARAMS;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TIME;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_CLASS;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_PACKAGE;
-
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.inputmethodservice.cts.common.DeviceEventConstants;
import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants;
import android.inputmethodservice.cts.common.test.TestInfo;
import android.inputmethodservice.cts.db.Entity;
@@ -38,16 +35,8 @@
import android.inputmethodservice.cts.db.Table;
import android.os.SystemClock;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
import android.util.Log;
-import com.android.json.stream.JsonReader;
-import com.android.json.stream.JsonWriter;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -62,85 +51,21 @@
public static final Table<DeviceEvent> TABLE = new DeviceEventTable(EventTableConstants.NAME);
- public static IntentBuilder builder() {
- return new IntentBuilder();
- }
-
/**
- * Builder to create an intent to send a device event.
- * The built intent that has event {@code sender}, {@code type}, {@code paramsString}, time from
- * {@link SystemClock#uptimeMillis()}, and target component of event receiver.
- *
+ * Create an intent to send a device event.
+ * @param sender an event sender.
+ * @param type an event type defined at {@link DeviceEventType}.
+ * @return an intent that has event {@code sender}, {@code type}, time from
+ * {@link SystemClock#uptimeMillis()}, and target component of event receiver.
*/
- public static final class IntentBuilder {
- String mSender;
- DeviceEventType mType;
- JsonWriter mJsonWriter;
- StringWriter mStringWriter;
-
- /**
- * @param type an event type defined at {@link DeviceEventType}.
- */
- public IntentBuilder setType(DeviceEventType type) {
- mType = type;
- return this;
- }
-
- /**
- * @param sender an event sender.
- */
- public void setSender(String sender) {
- mSender = sender;
- }
-
- public IntentBuilder with(DeviceEventTypeParam eventParam, boolean value) {
- appendToJson(eventParam, value);
- return this;
- }
-
- public Intent build() {
- Intent intent = new Intent()
- .setAction(ACTION_DEVICE_EVENT)
- .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
- .putExtra(EXTRA_EVENT_SENDER, mSender)
- .putExtra(EXTRA_EVENT_TYPE, mType.name())
- .putExtra(EXTRA_EVENT_PARAMS, getJsonString())
- .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
-
- mJsonWriter = null;
- mStringWriter = null;
- return intent;
- }
-
- private String getJsonString() {
- if (mJsonWriter == null) {
- return "";
- }
- try {
- mJsonWriter.endObject();
- mJsonWriter.flush();
- } catch (IOException e) {
- throw new RuntimeException("IntentBuilder.getJsonString() failed.", e);
- }
- return mStringWriter.toString();
- }
-
- private void appendToJson(DeviceEventTypeParam eventParam, boolean value) {
- final String key = eventParam.getName();
- if (TextUtils.isEmpty(key)) {
- return;
- }
- try {
- if (mJsonWriter == null) {
- mStringWriter = new StringWriter();
- mJsonWriter = new JsonWriter(mStringWriter);
- mJsonWriter.beginObject();
- }
- mJsonWriter.name(key).value(value);
- } catch (IOException e) {
- throw new RuntimeException("IntentBuilder.appendToJson() failed.", e);
- }
- }
+ public static Intent newDeviceEventIntent(@NonNull final String sender,
+ @NonNull final DeviceEventType type) {
+ return new Intent()
+ .setAction(ACTION_DEVICE_EVENT)
+ .setClassName(RECEIVER_PACKAGE, RECEIVER_CLASS)
+ .putExtra(EXTRA_EVENT_SENDER, sender)
+ .putExtra(EXTRA_EVENT_TYPE, type.name())
+ .putExtra(EXTRA_EVENT_TIME, SystemClock.uptimeMillis());
}
/**
@@ -168,16 +93,7 @@
"Intent must have " + EXTRA_EVENT_TIME + ": " + intent);
}
- String paramsString = intent.getStringExtra(DeviceEventConstants.EXTRA_EVENT_PARAMS);
- if (paramsString == null) {
- paramsString = "";
- }
-
- return new DeviceEvent(
- sender,
- type,
- paramsString,
- intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
+ return new DeviceEvent(sender, type, intent.getLongExtra(EXTRA_EVENT_TIME, 0L));
}
/**
@@ -242,21 +158,13 @@
public final DeviceEventType type;
/**
- * Event parameters formatted as json string.
- * e.g. {@link DeviceEventTypeParam#ON_START_INPUT_RESTARTING}
- */
- public final String paramsString;
-
- /**
* Event time, value is from {@link SystemClock#uptimeMillis()}.
*/
public final long time;
- private DeviceEvent(
- final String sender, final DeviceEventType type, String paramsString, final long time) {
+ private DeviceEvent(final String sender, final DeviceEventType type, final long time) {
this.sender = sender;
this.type = type;
- this.paramsString = paramsString;
this.time = time;
}
@@ -266,37 +174,6 @@
}
/**
- * @param eventParam {@link DeviceEventTypeParam} to look for.
- * @param event {@link DeviceEvent} to look in.
- * @return Event parameter for provided key. If key is not found in
- * {@link DeviceEvent#paramsString}, null is returned.
- *
- * TODO: Support other primitive and custom types.
- */
- @Nullable
- public static Boolean getEventParamBoolean(
- DeviceEventTypeParam eventParam, final DeviceEvent event) {
- StringReader stringReader = new StringReader(event.paramsString);
- JsonReader reader = new JsonReader(stringReader);
-
- try {
- reader.beginObject();
- while (reader.hasNext()) {
- String name = reader.nextName();
- if (name.equals(eventParam.getName())) {
- Boolean value = reader.nextBoolean();
- reader.endObject();
- return value;
- }
- }
- reader.endObject();
- } catch (IOException e) {
- throw new RuntimeException("DeviceEvent.getEventParamBoolean() failed.", e);
- }
- return null;
- }
-
- /**
* Abstraction of device event table in database.
*/
private static final class DeviceEventTable extends Table<DeviceEvent> {
@@ -306,19 +183,16 @@
private final Field SENDER;
private final Field TYPE;
private final Field TIME;
- private final Field PARAMS;
private DeviceEventTable(final String name) {
super(name, new Entity.Builder<DeviceEvent>()
.addField(EventTableConstants.SENDER, Cursor.FIELD_TYPE_STRING)
.addField(EventTableConstants.TYPE, Cursor.FIELD_TYPE_STRING)
.addField(EventTableConstants.TIME, Cursor.FIELD_TYPE_INTEGER)
- .addField(EventTableConstants.EXTRAS, Cursor.FIELD_TYPE_STRING)
.build());
SENDER = getField(EventTableConstants.SENDER);
TYPE = getField(EventTableConstants.TYPE);
TIME = getField(EventTableConstants.TIME);
- PARAMS = getField(EventTableConstants.EXTRAS);
}
@Override
@@ -326,7 +200,6 @@
final ContentValues values = new ContentValues();
SENDER.putString(values, event.sender);
TYPE.putString(values, event.type.name());
- PARAMS.putString(values, event.paramsString);
TIME.putLong(values, event.time);
return values;
}
@@ -341,7 +214,6 @@
final DeviceEvent event = new DeviceEvent(
SENDER.getString(cursor),
DeviceEventType.valueOf(TYPE.getString(cursor)),
- PARAMS.getString(cursor),
TIME.getLong(cursor));
builder.accept(event);
if (DEBUG_STREAM) {
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
index b37873c..6ae3568 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
@@ -16,21 +16,18 @@
package android.inputmethodservice.cts.ime;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.HIDE_SOFT_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT_VIEW;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.SHOW_SOFT_INPUT;
+import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.cts.DeviceEvent;
-import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventTypeParam;
+import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
-
-import android.os.ResultReceiver;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -45,33 +42,13 @@
new ImeCommandReceiver<>();
private String mLogTag;
- private class CtsInputMethodImpl extends InputMethodImpl {
- @Override
- public void showSoftInput(int flags, ResultReceiver resultReceiver) {
- sendEvent(DeviceEvent.builder().setType(SHOW_SOFT_INPUT));
- if (DEBUG) {
- Log.d(mLogTag, "showSoftInput called");
- }
- super.showSoftInput(flags, resultReceiver);
- }
-
- @Override
- public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
- sendEvent(DeviceEvent.builder().setType(HIDE_SOFT_INPUT));
- if (DEBUG) {
- Log.d(mLogTag, "hideSoftInput called");
- }
- super.hideSoftInput(flags, resultReceiver);
- }
- }
-
@Override
public void onCreate() {
mLogTag = getClass().getSimpleName();
if (DEBUG) {
Log.d(mLogTag, "onCreate:");
}
- sendEvent(DeviceEvent.builder().setType(ON_CREATE));
+ sendEvent(ON_CREATE);
super.onCreate();
@@ -85,10 +62,8 @@
+ " editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
+ sendEvent(ON_START_INPUT, editorInfo, restarting);
- sendEvent(DeviceEvent.builder()
- .setType(ON_START_INPUT)
- .with(DeviceEventTypeParam.ON_START_INPUT_RESTARTING, restarting));
super.onStartInput(editorInfo, restarting);
}
@@ -99,8 +74,7 @@
+ " editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
-
- sendEvent(DeviceEvent.builder().setType(ON_START_INPUT_VIEW));
+ sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
super.onStartInputView(editorInfo, restarting);
}
@@ -110,7 +84,7 @@
if (DEBUG) {
Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
}
- sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT_VIEW));
+ sendEvent(ON_FINISH_INPUT_VIEW, finishingInput);
super.onFinishInputView(finishingInput);
}
@@ -120,7 +94,7 @@
if (DEBUG) {
Log.d(mLogTag, "onFinishInput:");
}
- sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT));
+ sendEvent(ON_FINISH_INPUT);
super.onFinishInput();
}
@@ -130,23 +104,13 @@
if (DEBUG) {
Log.d(mLogTag, "onDestroy:");
}
- sendEvent(DeviceEvent.builder().setType(ON_DESTROY));
+ sendEvent(ON_DESTROY);
super.onDestroy();
unregisterReceiver(mImeCommandReceiver);
}
- @Override
- public AbstractInputMethodImpl onCreateInputMethodInterface() {
- final CtsInputMethodImpl inputMethod = new CtsInputMethodImpl();
- if (DEBUG) {
- Log.d(mLogTag, "onCreateInputMethodInterface");
- }
-
- return inputMethod;
- }
-
//
// Implementations of {@link ImeCommandCallbacks}.
//
@@ -178,8 +142,10 @@
}
}
- private void sendEvent(final DeviceEvent.IntentBuilder intentBuilder) {
- intentBuilder.setSender(getClass().getName());
- sendBroadcast(intentBuilder.build());
+ private void sendEvent(final DeviceEventType type, final Object... args) {
+ final String sender = getClass().getName();
+ final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
+ // TODO: Send arbitrary {@code args} in {@code intent}.
+ sendBroadcast(intent);
}
}
diff --git a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
index bf7e9a7..0b9dba6 100644
--- a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
+++ b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
@@ -16,6 +16,7 @@
-->
<configuration description="Config for CTS Input Method Service host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
index 02c3e55..bdf0983 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -22,7 +22,6 @@
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_COMPONENT;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -33,33 +32,34 @@
import android.inputmethodservice.cts.common.test.ShellCommandUtils;
import android.inputmethodservice.cts.common.test.TestInfo;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class InputMethodServiceLifecycleTest extends CompatibilityHostTestBase {
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
- private String mDefaultImeId;
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class InputMethodServiceLifecycleTest extends BaseHostJUnit4Test {
+
+ private static final long TIMEOUT = TimeUnit.MICROSECONDS.toMillis(20000);
+ private static final long POLLING_INTERVAL = TimeUnit.MICROSECONDS.toMillis(200);
@Before
public void setUp() throws Exception {
// Skip whole tests when DUT has no android.software.input_methods feature.
- assumeTrue(Boolean.parseBoolean(shell(
- ShellCommandUtils.hasFeature(ShellCommandUtils.FEATURE_INPUT_METHODS))));
- mDefaultImeId = shell(ShellCommandUtils.getCurrentIme());
+ assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
cleanUpTestImes();
shell(ShellCommandUtils.deleteContent(EventTableConstants.CONTENT_URI));
}
@After
public void tearDown() throws Exception {
- shell(ShellCommandUtils.setCurrentIme(mDefaultImeId));
- cleanUpTestImes();
+ shell(ShellCommandUtils.resetImes());
}
@Test
@@ -78,69 +78,30 @@
@Test
public void testUninstallCurrentIme() throws Exception {
- installAndSetIme1();
-
- final TestInfo testIme1IsNotCurrentIme = new TestInfo(DeviceTestConstants.PACKAGE,
- DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
- sendTestStartEvent(testIme1IsNotCurrentIme);
- uninstallPackageIfExists(Ime1Constants.PACKAGE);
- assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
- assertEquals(shell(ShellCommandUtils.getCurrentIme()), mDefaultImeId);
- }
-
- @Test
- public void testDisableCurrentIme() throws Exception {
- installAndSetIme1();
-
- final TestInfo testIme1IsNotCurrentIme = new TestInfo(DeviceTestConstants.PACKAGE,
- DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_IME1_IS_NOT_CURRENT_IME);
- sendTestStartEvent(testIme1IsNotCurrentIme);
- shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
- assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
- assertEquals(shell(ShellCommandUtils.getCurrentIme()), mDefaultImeId);
- }
-
- @Test
- public void testSearchView_giveFocusShowIme() throws Exception {
- installAndSetIme1();
-
- final TestInfo testGiveFocusShowIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
- DeviceTestConstants.TEST_CLASS,
- DeviceTestConstants.TEST_SEARCH_VIEW_GIVE_FOCUS_SHOW_IME1);
- sendTestStartEvent(testGiveFocusShowIme1);
- assertTrue(runDeviceTestMethod(testGiveFocusShowIme1));
- }
-
- @Test
- public void testSearchView_setQueryHideIme() throws Exception {
- installAndSetIme1();
-
- final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
- DeviceTestConstants.TEST_CLASS,
- DeviceTestConstants.TEST_SEARCH_VIEW_SET_QUERY_HIDE_IME1);
- sendTestStartEvent(testSetQueryHideIme1);
- assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
- }
-
- @Test
- public void testOnStartInputCalledOnce() throws Exception {
- installAndSetIme1();
-
- final TestInfo testSetQueryHideIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
- DeviceTestConstants.TEST_CLASS,
- DeviceTestConstants.TEST_ON_START_INPUT_CALLED_ONCE_IME1);
- sendTestStartEvent(testSetQueryHideIme1);
- assertTrue(runDeviceTestMethod(testSetQueryHideIme1));
- }
-
- private void installAndSetIme1() throws Exception {
final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
- DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
+ DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
sendTestStartEvent(testCreateIme1);
installPackage(Ime1Constants.APK, "-r");
shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
assertTrue(runDeviceTestMethod(testCreateIme1));
+
+ uninstallPackageIfExists(Ime1Constants.PACKAGE);
+ assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, TIMEOUT);
+ }
+
+ @Test
+ public void testDisableCurrentIme() throws Exception {
+ final TestInfo testCreateIme1 = new TestInfo(DeviceTestConstants.PACKAGE,
+ DeviceTestConstants.TEST_CLASS, DeviceTestConstants.TEST_CREATE_IME1);
+ sendTestStartEvent(testCreateIme1);
+ installPackage(Ime1Constants.APK, "-r");
+ shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID));
+ shell(ShellCommandUtils.setCurrentIme(Ime1Constants.IME_ID));
+ assertTrue(runDeviceTestMethod(testCreateIme1));
+
+ shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
+ assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, TIMEOUT);
}
private void sendTestStartEvent(final TestInfo deviceTest) throws Exception {
@@ -166,8 +127,28 @@
}
private void uninstallPackageIfExists(final String packageName) throws Exception {
- if (isPackageInstalled(packageName)) {
- uninstallPackage(packageName);
+ if (isPackageInstalled(getDevice(), packageName)) {
+ uninstallPackage(getDevice(), packageName);
+ }
+ }
+
+ /**
+ * Makes sure that the given IME is not in the stored in the secure settings as the current IME.
+ *
+ * @param imeId IME ID to be monitored
+ * @param timeout timeout in millisecond
+ */
+ private void assertImeNotSelectedInSecureSettings(String imeId, long timeout) throws Exception {
+ while (true) {
+ if (timeout < 0) {
+ throw new TimeoutException(imeId + " is still the current IME even after "
+ + timeout + " msec.");
+ }
+ if (!imeId.equals(shell(ShellCommandUtils.getCurrentIme()))) {
+ break;
+ }
+ Thread.sleep(POLLING_INTERVAL);
+ timeout -= POLLING_INTERVAL;
}
}
}
diff --git a/hostsidetests/jdwpsecurity/AndroidTest.xml b/hostsidetests/jdwpsecurity/AndroidTest.xml
index 4b23d19..f062508 100644
--- a/hostsidetests/jdwpsecurity/AndroidTest.xml
+++ b/hostsidetests/jdwpsecurity/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS JDWP host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsJdwpSecurityHostTestCases.jar" />
diff --git a/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml b/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
index 706bdef..21a5cc7 100644
--- a/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
+++ b/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/attaching/host/AndroidTest.xml b/hostsidetests/jvmti/attaching/host/AndroidTest.xml
index 41c161d..710b751 100644
--- a/hostsidetests/jvmti/attaching/host/AndroidTest.xml
+++ b/hostsidetests/jvmti/attaching/host/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI Attaching test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/redefining/AndroidTest.xml b/hostsidetests/jvmti/redefining/AndroidTest.xml
index b1f9b01..1d04cb9 100644
--- a/hostsidetests/jvmti/redefining/AndroidTest.xml
+++ b/hostsidetests/jvmti/redefining/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml
index 69cf790..42e8698 100644
--- a/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-902/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml
index 3bec8b1..4b33b2d 100644
--- a/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-903/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml
index 577c202..57f6027 100644
--- a/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-904/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml
index 96ef377..a0e2e05 100644
--- a/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-905/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml
index 217c127..38d2abe 100644
--- a/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-906/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml
index f6662a9..af587f3 100644
--- a/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-907/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml
index 94cc519..c2feb1a 100644
--- a/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-908/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml
index efefdf6..32f135f 100644
--- a/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-910/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml
index c6a4565..1832662 100644
--- a/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-911/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml
index 7cb82f0..4ba486e 100644
--- a/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-912/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml
index 7dff2cf..a24a927 100644
--- a/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-913/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml
index f6ca1e7..efbc392 100644
--- a/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-914/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml
index b8e03bf..02239d7 100644
--- a/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-915/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml
index 0098aef..5726e10 100644
--- a/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-917/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml
index 78f0927..7d9b72a 100644
--- a/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-918/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml
index d23c3fc..eb22191 100644
--- a/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-919/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml
index ff574cc..9c6d09b 100644
--- a/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-920/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml
index 6c9f7dc..c2e21f1 100644
--- a/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-922/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml
index cae6301..f9c586a 100644
--- a/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-923/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml
index 35bea67..fa355f2 100644
--- a/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-924/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml
index ea30af1..a50a51d 100644
--- a/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-926/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml
index f09082a..5c02f07 100644
--- a/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-927/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml
index 7755ea0..bf6f02c 100644
--- a/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-928/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml
index c2b8745..2599ea5 100644
--- a/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-930/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml
index 349cc80..2f8a15d 100644
--- a/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-931/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml
index c691b40..aa2b9db 100644
--- a/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-932/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml
index fe8f6df..2f35ce7 100644
--- a/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-940/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml
index f200441..bad7710 100644
--- a/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-942/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml
index 97a69cd..d13dbc9 100644
--- a/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-944/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml
index 3c76674..dc7254a 100644
--- a/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-945/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml
index 1f7a004..cd5586e 100644
--- a/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-947/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml
index ef9a6e7..844b5a4 100644
--- a/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-951/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml
index 0470da1..102db35 100644
--- a/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-982/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml
index febd68b..33eedb0 100644
--- a/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-984/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml
index ff85148..684833d 100644
--- a/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-985/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml
index 3205637..170193e 100644
--- a/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml
+++ b/hostsidetests/jvmti/run-tests/test-986/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/jvmti/tagging/AndroidTest.xml b/hostsidetests/jvmti/tagging/AndroidTest.xml
index 403ca28..be75c08 100644
--- a/hostsidetests/jvmti/tagging/AndroidTest.xml
+++ b/hostsidetests/jvmti/tagging/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JVMTI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/media/AndroidTest.xml b/hostsidetests/media/AndroidTest.xml
index 483d969..e5818b5 100644
--- a/hostsidetests/media/AndroidTest.xml
+++ b/hostsidetests/media/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS media host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsMediaHostTestCases.jar" />
diff --git a/hostsidetests/media/app/MediaSessionTest/Android.mk b/hostsidetests/media/app/MediaSessionTest/Android.mk
index fb0fe40..7303147 100644
--- a/hostsidetests/media/app/MediaSessionTest/Android.mk
+++ b/hostsidetests/media/app/MediaSessionTest/Android.mk
@@ -29,7 +29,10 @@
$(call all-java-files-under, ../../common)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+
LOCAL_SDK_VERSION := current
diff --git a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
index 5ae6f96..18060e1 100644
--- a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
@@ -21,6 +21,8 @@
<application
android:testOnly="true">
+ <uses-library android:name="android.test.runner" />
+
<service
android:name=".MediaSessionManagerTest"
android:label="MediaSessionManagerTest"
diff --git a/hostsidetests/media/bitstreams/AndroidTest.xml b/hostsidetests/media/bitstreams/AndroidTest.xml
index 6a60a65..1621aa7 100644
--- a/hostsidetests/media/bitstreams/AndroidTest.xml
+++ b/hostsidetests/media/bitstreams/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Sample host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
<option name="media-download-only" value="true" />
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
index 3c1f14e..e7be900 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
@@ -140,6 +140,8 @@
instrTest.addIncludeFilter(fullTestName);
instrTest.setTestTimeout(testTimeout);
instrTest.setShellTimeout(shellTimeout);
+ // disable rerun mode to avoid collecting tests first then running.
+ instrTest.setRerunMode(false);
for (Entry<String, String> e : getArgs().entrySet()) {
instrTest.addInstrumentationArg(e.getKey(), e.getValue());
}
diff --git a/hostsidetests/monkey/AndroidTest.xml b/hostsidetests/monkey/AndroidTest.xml
index 6479915..ad1507a 100644
--- a/hostsidetests/monkey/AndroidTest.xml
+++ b/hostsidetests/monkey/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS monkey host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsMonkeyTestCases.jar" />
diff --git a/hostsidetests/multiuser/Android.mk b/hostsidetests/multiuser/Android.mk
index 9827ca7..e4c654d 100644
--- a/hostsidetests/multiuser/Android.mk
+++ b/hostsidetests/multiuser/Android.mk
@@ -22,7 +22,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed
+LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed platform-test-annotations-host
LOCAL_CTS_TEST_PACKAGE := android.host.multiuser
diff --git a/hostsidetests/multiuser/AndroidTest.xml b/hostsidetests/multiuser/AndroidTest.xml
index 27a2073..3c359c0 100644
--- a/hostsidetests/multiuser/AndroidTest.xml
+++ b/hostsidetests/multiuser/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS multiuser host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsMultiUserHostTestCases.jar" />
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
index 5d4b89e..0f83f74 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
@@ -16,8 +16,9 @@
package android.host.multiuser;
+import android.platform.test.annotations.Presubmit;
+
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
import java.util.LinkedHashSet;
import java.util.Scanner;
@@ -25,11 +26,12 @@
/**
* Test verifies that users can be created/switched to without error dialogs shown to the user
+ * Run: atest CreateUsersNoAppCrashesTest
*/
public class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
private int mInitialUserId;
private static final long LOGCAT_POLL_INTERVAL_MS = 5000;
- private static final long BOOT_COMPLETED_TIMEOUT_MS = 120000;
+ private static final long USER_SWITCH_COMPLETE_TIMEOUT_MS = 180000;
@Override
protected void setUp() throws Exception {
@@ -37,6 +39,7 @@
mInitialUserId = getDevice().getCurrentUser();
}
+ @Presubmit
public void testCanCreateGuestUser() throws Exception {
if (!mSupportsMultiUser) {
return;
@@ -45,31 +48,68 @@
"TestUser_" + System.currentTimeMillis() /* name */,
true /* guest */,
false /* ephemeral */);
- getDevice().executeAdbCommand("logcat", "-c"); // Reset log
- assertTrue("Couldn't switch to user " + userId, getDevice().switchUser(userId));
- Set<String> appErrors = new LinkedHashSet<>();
- assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors=" + appErrors,
- waitForBootCompleted(appErrors, userId));
- assertTrue("App error dialog(s) are present: " + appErrors, appErrors.isEmpty());
- assertTrue("Couldn't switch to user " + userId, getDevice().switchUser(mInitialUserId));
+ assertSwitchToNewUser(userId);
+ assertSwitchToUser(userId, mInitialUserId);
}
- private boolean waitForBootCompleted(Set<String> appErrors, int targetUserId)
- throws DeviceNotAvailableException, InterruptedException {
+ @Presubmit
+ public void testCanCreateSecondaryUser() throws Exception {
+ if (!mSupportsMultiUser) {
+ return;
+ }
+ int userId = getDevice().createUser(
+ "TestUser_" + System.currentTimeMillis() /* name */,
+ false /* guest */,
+ false /* ephemeral */);
+ assertSwitchToNewUser(userId);
+ assertSwitchToUser(userId, mInitialUserId);
+ }
+
+ private void assertSwitchToNewUser(int toUserId) throws Exception {
+ final String exitString = "Finished processing BOOT_COMPLETED for u" + toUserId;
+ final Set<String> appErrors = new LinkedHashSet<>();
+ getDevice().executeAdbCommand("logcat", "-c"); // Reset log
+ assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+ final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
+ assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors="
+ + appErrors, result);
+ assertTrue("App error dialog(s) are present: " + appErrors, appErrors.isEmpty());
+ }
+
+ private void assertSwitchToUser(int fromUserId, int toUserId) throws Exception {
+ final String exitString = "Continue user switch oldUser #" + fromUserId + ", newUser #"
+ + toUserId;
+ final Set<String> appErrors = new LinkedHashSet<>();
+ getDevice().executeAdbCommand("logcat", "-c"); // Reset log
+ assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+ final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
+ assertTrue("Didn't reach \"Continue user switch\" stage. appErrors=" + appErrors, result);
+ assertTrue("App error dialog(s) are present: " + appErrors, appErrors.isEmpty());
+ }
+
+ private boolean waitForUserSwitchComplete(Set<String> appErrors, int targetUserId,
+ String exitString) throws DeviceNotAvailableException, InterruptedException {
+ boolean mExitFound = false;
long ti = System.currentTimeMillis();
- while (System.currentTimeMillis() - ti < BOOT_COMPLETED_TIMEOUT_MS) {
- String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d");
+ while (System.currentTimeMillis() - ti < USER_SWITCH_COMPLETE_TIMEOUT_MS) {
+ String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
+ "ActivityManager:D", "AndroidRuntime:E", "*:S");
Scanner in = new Scanner(logs);
while (in.hasNextLine()) {
String line = in.nextLine();
if (line.contains("Showing crash dialog for package")) {
appErrors.add(line);
- } else if (line.contains("Finished processing BOOT_COMPLETED for u" + targetUserId)) {
- return true;
+ } else if (line.contains(exitString)) {
+ // Parse all logs in case crashes occur as a result of onUserChange callbacks
+ mExitFound = true;
+ } else if (line.contains("FATAL EXCEPTION IN SYSTEM PROCESS")) {
+ throw new IllegalStateException("System process crashed - " + line);
}
-
}
in.close();
+ if (mExitFound) {
+ return true;
+ }
Thread.sleep(LOGCAT_POLL_INTERVAL_MS);
}
return false;
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
index 9a43534..d0c96ec 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
@@ -56,8 +56,8 @@
}
final String setRestriction = "pm set-user-restriction no_fun ";
final String output = getDevice().executeShellCommand(setRestriction + "1");
- final boolean isErrorOutput = output.startsWith("Error")
- && output.contains("SecurityException");
+ final boolean isErrorOutput = output.contains("SecurityException")
+ && output.contains("You need MANAGE_USERS permission");
assertTrue("Trying to set user restriction should fail with SecurityException. "
+ "command output: " + output, isErrorOutput);
}
diff --git a/hostsidetests/net/AndroidTest.xml b/hostsidetests/net/AndroidTest.xml
index 4a2e2e3..c96fea4 100644
--- a/hostsidetests/net/AndroidTest.xml
+++ b/hostsidetests/net/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS net host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/hostsidetests/net/app/Android.mk b/hostsidetests/net/app/Android.mk
index f094f3f..c03e70b 100644
--- a/hostsidetests/net/app/Android.mk
+++ b/hostsidetests/net/app/Android.mk
@@ -23,6 +23,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ub-uiautomator \
CtsHostsideNetworkTestsAidl
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsHostsideNetworkTestsApp
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index d2c0873..133a43b 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -127,6 +127,19 @@
assertBackgroundNetworkAccess(false);
}
+ public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
+ if (!isSupported()) return;
+
+ setAppIdle(true);
+ assertBackgroundNetworkAccess(false);
+
+ addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(true);
+ // Wait until the whitelist duration is expired.
+ SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(false);
+ }
+
public void testBackgroundNetworkAccess_disabled() throws Exception {
if (!isSupported()) return;
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index ce56d25..254b009 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -108,6 +108,8 @@
private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
+ protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
+
protected Context mContext;
protected Instrumentation mInstrumentation;
protected ConnectivityManager mCm;
@@ -138,6 +140,7 @@
enableLocation();
}
mSupported = setUpActiveNetworkMeteringState();
+ setAppIdle(false);
Log.i(TAG, "Apps status on " + getName() + ":\n"
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
@@ -751,6 +754,12 @@
+ ". Full list: " + uids);
}
+ protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
+ throws Exception {
+ Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
+ executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
+ }
+
protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
throws Exception {
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
index 5248255..76332be 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -15,6 +15,7 @@
*/
package com.android.cts.net.hostside;
+import android.os.SystemClock;
import android.util.Log;
/**
@@ -271,4 +272,55 @@
setDozeMode(false);
}
}
+
+ public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
+ if (!isSupported()) {
+ return;
+ }
+ if (!isDozeModeEnabled()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Doze Mode");
+ return;
+ }
+
+ setDozeMode(true);
+ setAppIdle(true);
+
+ try {
+ assertBackgroundNetworkAccess(false);
+
+ addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(true);
+
+ // Wait until the whitelist duration is expired.
+ SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(false);
+ } finally {
+ setAppIdle(false);
+ setDozeMode(false);
+ }
+ }
+
+ public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
+ if (!isSupported()) {
+ return;
+ }
+
+ setBatterySaverMode(true);
+ setAppIdle(true);
+
+ try {
+ assertBackgroundNetworkAccess(false);
+
+ addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(true);
+
+ // Wait until the whitelist duration is expired.
+ SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(false);
+ } finally {
+ setAppIdle(false);
+ setBatterySaverMode(false);
+ }
+ }
}
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index bf3fc08..fe9d36c 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -146,6 +146,11 @@
"testBackgroundNetworkAccess_whitelisted");
}
+ public void testAppIdleMetered_tempWhitelisted() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ "testBackgroundNetworkAccess_tempWhitelisted");
+ }
+
public void testAppIdleMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_enabled");
@@ -166,6 +171,11 @@
"testBackgroundNetworkAccess_whitelisted");
}
+ public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ "testBackgroundNetworkAccess_tempWhitelisted");
+ }
+
public void testAppIdleNonMetered_enabled() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
@@ -261,6 +271,16 @@
"testDozeAndAppIdle_powerSaveWhitelists");
}
+ public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ "testAppIdleAndDoze_tempPowerSaveWhitelists");
+ }
+
+ public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ "testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
+ }
+
/*******************
* Helper methods. *
*******************/
diff --git a/hostsidetests/numberblocking/AndroidTest.xml b/hostsidetests/numberblocking/AndroidTest.xml
index 9a6d805..6ebc522 100644
--- a/hostsidetests/numberblocking/AndroidTest.xml
+++ b/hostsidetests/numberblocking/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS number blocking test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="abuse" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/numberblocking/app/Android.mk b/hostsidetests/numberblocking/app/Android.mk
index cfbccbe..fc0f4a0 100644
--- a/hostsidetests/numberblocking/app/Android.mk
+++ b/hostsidetests/numberblocking/app/Android.mk
@@ -27,7 +27,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 261c29d..2e61a61 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS OS host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
index cbeb342..a1550a6 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
@@ -23,6 +23,8 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application>
+ <uses-library android:name="android.test.runner" />
+
<uses-static-library
android:name="foo.bar.lib"
android:version="1"
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java
index dca3699..dbadeb9 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/UseSharedLibraryTest.java
@@ -187,7 +187,7 @@
// Make sure we see the lib we depend on via getting installed packages
List<PackageInfo> installedPackages = InstrumentationRegistry.getInstrumentation()
.getContext().getPackageManager().getInstalledPackages(0);
- int usedLibraryVersionCode = -1;
+ long usedLibraryVersionCode = -1;
for (PackageInfo installedPackage : installedPackages) {
if (STATIC_LIB_PROVIDER_PKG.equals(installedPackage.packageName)) {
if (usedLibraryVersionCode != -1) {
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index c87969b..777479c 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Sample host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java
index f32c523..0fbf3b9 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4DeviceTest.java
@@ -16,14 +16,14 @@
package android.sample.cts;
-import com.android.compatibility.common.tradefed.testtype.CompatibilityHostTestBase;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.runner.RunWith;
import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Test that collects test results from test package android.sample.cts.app2.
@@ -33,7 +33,7 @@
* collected from the hostside and reported accordingly.
*/
@RunWith(DeviceJUnit4ClassRunner.class)
-public class SampleHostJUnit4DeviceTest extends CompatibilityHostTestBase {
+public class SampleHostJUnit4DeviceTest extends BaseHostJUnit4Test {
private static final String TEST_PKG = "android.sample.cts.app2";
private static final String TEST_CLASS = TEST_PKG + "." + "SampleDeviceTest";
@@ -65,7 +65,7 @@
@After
public void tearDown() throws Exception {
- uninstallPackage(TEST_PKG);
+ uninstallPackage(getDevice(), TEST_PKG);
}
}
diff --git a/hostsidetests/seccomp/Android.mk b/hostsidetests/seccomp/Android.mk
new file mode 100644
index 0000000..2c1c077
--- /dev/null
+++ b/hostsidetests/seccomp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := CtsSeccompHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/seccomp/AndroidTest.xml b/hostsidetests/seccomp/AndroidTest.xml
new file mode 100644
index 0000000..cbfd1c4
--- /dev/null
+++ b/hostsidetests/seccomp/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Sseccomp host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="misc" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsSeccompDeviceApp.apk" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsSeccompHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/seccomp/app/Android.mk b/hostsidetests/seccomp/app/Android.mk
new file mode 100644
index 0000000..726e64b
--- /dev/null
+++ b/hostsidetests/seccomp/app/Android.mk
@@ -0,0 +1,56 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Include both the 32 and 64 bit versions
+LOCAL_MULTILIB := both
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+ libctsseccomp_jni \
+ libcts_jni \
+ libnativehelper_compat_libc++ \
+ libnativehelper \
+ libcutils \
+ libc++ \
+ libpackagelistparser \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsSeccompDeviceApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/seccomp/app/AndroidManifest.xml b/hostsidetests/seccomp/app/AndroidManifest.xml
new file mode 100644
index 0000000..b8e97e3
--- /dev/null
+++ b/hostsidetests/seccomp/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.seccomp.cts.app">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.seccomp.cts.app" />
+
+</manifest>
diff --git a/hostsidetests/seccomp/app/jni/Android.mk b/hostsidetests/seccomp/app/jni/Android.mk
new file mode 100644
index 0000000..a0604a1
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctsseccomp_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ CtsSeccompJniOnLoad.cpp \
+ android_seccomp_cts_app_SeccompDeviceTest.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libnativehelper \
+ liblog \
+ libcutils \
+ libc++ \
+ libpackagelistparser \
+
+
+LOCAL_C_INCLUDES += ndk/sources/cpufeatures
+LOCAL_STATIC_LIBRARIES := cpufeatures
+
+LOCAL_CFLAGS := -Wall -Werror
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/hostsidetests/seccomp/app/jni/CtsSeccompJniOnLoad.cpp b/hostsidetests/seccomp/app/jni/CtsSeccompJniOnLoad.cpp
new file mode 100644
index 0000000..928b8c5
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/CtsSeccompJniOnLoad.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_seccomp_cts_app_SeccompTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM *vm, void * /*reserved*/) {
+ JNIEnv *env = NULL;
+
+ if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ return JNI_ERR;
+ }
+
+ if (register_android_seccomp_cts_app_SeccompTest(env)) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_4;
+}
diff --git a/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
new file mode 100644
index 0000000..de82b44
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+#define LOG_TAG "SeccompTest"
+
+#include <cutils/log.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/*
+ * Function: testSyscallBlocked
+ * Purpose: test that the syscall listed is blocked by seccomp
+ * Parameters:
+ * nr: syscall number
+ * Returns:
+ * 1 if blocked, else 0
+ * Exceptions: None
+ */
+static jboolean testSyscallBlocked(JNIEnv *, jobject, int nr) {
+ int pid = fork();
+ if (pid == 0) {
+ ALOGI("Calling syscall %d", nr);
+ syscall(nr);
+ return false;
+ } else {
+ int status;
+ int ret = waitpid(pid, &status, 0);
+ if (ret != pid) {
+ ALOGE("Unexpected return result from waitpid");
+ return false;
+ }
+
+ if (WIFEXITED(status)) {
+ ALOGE("syscall was not blocked");
+ return false;
+ }
+
+ if (WIFSIGNALED(status)) {
+ int signal = WTERMSIG(status);
+ if (signal == 31) {
+ ALOGI("syscall caused process termination");
+ return true;
+ }
+
+ ALOGE("Unexpected signal");
+ return false;
+ }
+
+ ALOGE("Unexpected status from syscall_exists");
+ return false;
+ }
+}
+
+static JNINativeMethod gMethods[] = {
+ { "testSyscallBlocked", "(I)Z",
+ (void*) testSyscallBlocked },
+};
+
+int register_android_seccomp_cts_app_SeccompTest(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("android/seccomp/cts/app/SeccompDeviceTest");
+
+ return env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
new file mode 100644
index 0000000..2a7bcb3
--- /dev/null
+++ b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.seccomp.cts.app;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.support.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.CpuFeatures;
+
+/**
+ * Device-side tests for CtsSeccompHostTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SeccompDeviceTest {
+ static {
+ System.loadLibrary("ctsseccomp_jni");
+ }
+
+ @Test
+ public void testCTSSyscallBlocked() {
+ if (CpuFeatures.isArm64Cpu()) {
+ testBlocked(217); // __NR_add_key
+ testBlocked(219); // __NR_keyctl
+ testAllowed(56); // __NR_openat
+
+ // b/35034743 - do not remove test without reading bug
+ testAllowed(267); // __NR_fstatfs64
+ } else if (CpuFeatures.isArmCpu()) {
+ testBlocked(309); // __NR_add_key
+ testBlocked(311); // __NR_keyctl
+ testAllowed(322); // __NR_openat
+
+ // b/35906875 - do not remove test without reading bug
+ testAllowed(316); // __NR_inotify_init
+ } else if (CpuFeatures.isX86_64Cpu()) {
+ testBlocked(248); // __NR_add_key
+ testBlocked(250); // __NR_keyctl
+ testAllowed(257); // __NR_openat
+ } else if (CpuFeatures.isX86Cpu()) {
+ testBlocked(286); // __NR_add_key
+ testBlocked(288); // __NR_keyctl
+ testAllowed(295); // __NR_openat
+ } else if (CpuFeatures.isMips64Cpu()) {
+ testBlocked(5239); // __NR_add_key
+ testBlocked(5241); // __NR_keyctl
+ testAllowed(5247); // __NR_openat
+ } else if (CpuFeatures.isMipsCpu()) {
+ testBlocked(4280); // __NR_add_key
+ testBlocked(4282); // __NR_keyctl
+ testAllowed(4288); // __NR_openat
+ } else {
+ Assert.fail("Unsupported OS");
+ }
+ }
+
+ @Test
+ public void testCTSSwapOnOffBlocked() {
+ if (CpuFeatures.isArm64Cpu()) {
+ testBlocked(224); // __NR_swapon
+ testBlocked(225); // __NR_swapoff
+ } else if (CpuFeatures.isArmCpu()) {
+ testBlocked(87); // __NR_swapon
+ testBlocked(115); // __NR_swapoff
+ } else if (CpuFeatures.isX86_64Cpu()) {
+ testBlocked(167); // __NR_swapon
+ testBlocked(168); // __NR_swapoff
+ } else if (CpuFeatures.isX86Cpu()) {
+ testBlocked(87); // __NR_swapon
+ testBlocked(115); // __NR_swapoff
+ } else if (CpuFeatures.isMips64Cpu()) {
+ testBlocked(5162); // __NR_swapon
+ testBlocked(5163); // __NR_swapoff
+ } else if (CpuFeatures.isMipsCpu()) {
+ testBlocked(4087); // __NR_swapon
+ testBlocked(4115); // __NR_swapoff
+ } else {
+ Assert.fail("Unsupported OS");
+ }
+ }
+
+ private void testBlocked(int nr) {
+ Assert.assertTrue("Syscall " + nr + " not blocked", testSyscallBlocked(nr));
+ }
+
+ private void testAllowed(int nr) {
+ Assert.assertFalse("Syscall " + nr + " blocked", testSyscallBlocked(nr));
+ }
+
+ private static final native boolean testSyscallBlocked(int nr);
+}
diff --git a/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java b/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
new file mode 100644
index 0000000..63258e3
--- /dev/null
+++ b/hostsidetests/seccomp/src/android/seccomp/cts/SeccompHostJUnit4DeviceTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.seccomp.cts;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that collects test results from test package android.seccomp.cts.app.
+ *
+ * When this test builds, it also builds a support APK containing
+ * {@link android.seccomp.cts.app.SeccompDeviceTest}, the results of which are
+ * collected from the hostside and reported accordingly.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SeccompHostJUnit4DeviceTest extends BaseHostJUnit4Test {
+
+ private static final String TEST_PKG = "android.seccomp.cts.app";
+ private static final String TEST_CLASS = TEST_PKG + "." + "SeccompDeviceTest";
+ private static final String TEST_APP = "CtsSeccompDeviceApp.apk";
+
+ private static final String TEST_CTS_SYSCALL_BLOCKED = "testCTSSyscallBlocked";
+ private static final String TEST_CTS_SWAP_ON_OFF_BLOCKED = "testCTSSwapOnOffBlocked";
+
+ @Before
+ public void setUp() throws Exception {
+ installPackage(TEST_APP);
+ }
+
+ @Test
+ public void testCTSSyscallBlocked() throws Exception {
+ Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, TEST_CTS_SYSCALL_BLOCKED));
+ }
+
+ @Test
+ public void testCTSSwapOnOffBlocked() throws Exception {
+ Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, TEST_CTS_SWAP_ON_OFF_BLOCKED));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ uninstallPackage(getDevice(), TEST_PKG);
+ }
+
+}
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index 00c5742..79325e4 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -54,6 +54,7 @@
$(HOST_OUT_EXECUTABLES)/checkseapp \
$(HOST_OUT_EXECUTABLES)/checkfc \
$(HOST_OUT_EXECUTABLES)/property_info_checker \
+ $(HOST_OUT_EXECUTABLES)/searchpolicy \
$(HOST_OUT_EXECUTABLES)/sepolicy_tests \
$(HOST_OUT_EXECUTABLES)/treble_sepolicy_tests \
$(HOST_OUT)/lib64/libsepolwrap.$(SHAREDLIB_EXT) \
diff --git a/hostsidetests/security/AndroidTest.xml b/hostsidetests/security/AndroidTest.xml
index eda596c..1bba585 100644
--- a/hostsidetests/security/AndroidTest.xml
+++ b/hostsidetests/security/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS Security host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
index d8b2816..d878342 100644
--- a/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/cts/security/FileSystemPermissionTest.java
@@ -1,12 +1,28 @@
package android.cts.security;
+import static android.security.cts.SELinuxHostTest.copyResourceToTempFile;
+import static android.security.cts.SELinuxHostTest.getDevicePolicyFile;
+import static android.security.cts.SELinuxHostTest.isMac;
+
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceTestCase;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
public class FileSystemPermissionTest extends DeviceTestCase {
@@ -52,4 +68,184 @@
private static String getInsecureDeviceAdbCommand(String path, String type) {
return String.format(INSECURE_DEVICE_ADB_COMMAND, path, type);
}
+
+ private static String HW_RNG_DEVICE = "/dev/hw_random";
+
+ public void testDevHwRandomPermissions() throws Exception {
+ // This test asserts that, if present, /dev/hw_random must:
+ // 1. Be owned by UID root and GID system
+ // 2. Have file permissions 0440 (only readable, and only by owner and group). The reason
+ // for being not readable by all/other is to avoid apps reading from this device.
+ // Firstly, /dev/hw_random is not public API for apps. Secondly, apps might erroneously
+ // use the output of Hardware RNG as trusted random output. Android does not trust output
+ // of /dev/hw_random. HW RNG output is only used for mixing into Linux RNG as untrusted
+ // input.
+ // 3. Be a character device with major:minor 10:183 -- hwrng kernel driver is using MAJOR 10
+ // and MINOR 183
+ // 4. Be openable and readable by system_server according to SELinux policy
+
+ if (!mDevice.doesFileExist(HW_RNG_DEVICE)) {
+ // Hardware RNG device is missing. This is OK because it is not required to be exposed
+ // on all devices.
+ return;
+ }
+
+ String command = "ls -l " + HW_RNG_DEVICE;
+ String output = mDevice.executeShellCommand(command).trim();
+ if (!output.endsWith(" " + HW_RNG_DEVICE)) {
+ fail("Unexpected output from " + command + ": \"" + output + "\"");
+ }
+ String[] outputWords = output.split("\\s");
+ assertEquals("Wrong file mode on " + HW_RNG_DEVICE, "cr--r-----", outputWords[0]);
+ assertEquals("Wrong owner of " + HW_RNG_DEVICE, "root", outputWords[2]);
+ assertEquals("Wrong group of " + HW_RNG_DEVICE, "system", outputWords[3]);
+ assertEquals("Wrong device major on " + HW_RNG_DEVICE, "10,", outputWords[4]);
+ assertEquals("Wrong device minor on " + HW_RNG_DEVICE, "183", outputWords[5]);
+
+ command = "ls -Z " + HW_RNG_DEVICE;
+ output = mDevice.executeShellCommand(command).trim();
+ assertEquals(
+ "Wrong SELinux label on " + HW_RNG_DEVICE,
+ "u:object_r:hw_random_device:s0 " + HW_RNG_DEVICE,
+ output);
+
+ File sepolicy = getDevicePolicyFile(mDevice);
+ output =
+ new String(
+ execSearchPolicy(
+ "--allow",
+ "-s", "system_server",
+ "-t", "hw_random_device",
+ "-c", "chr_file",
+ "-p", "open",
+ sepolicy.getPath()));
+ if (output.trim().isEmpty()) {
+ fail("SELinux policy does not permit system_server to open " + HW_RNG_DEVICE);
+ }
+ output =
+ new String(
+ execSearchPolicy(
+ "--allow",
+ "-s", "system_server",
+ "-t", "hw_random_device",
+ "-c", "chr_file",
+ "-p", "read",
+ sepolicy.getPath()));
+ if (output.trim().isEmpty()) {
+ fail("SELinux policy does not permit system_server to read " + HW_RNG_DEVICE);
+ }
+ }
+
+ /**
+ * Executes {@code searchpolicy} executable with the provided parameters and returns the
+ * contents of standard output.
+ *
+ * @throws IOException if execution of searchpolicy fails, returns non-zero error code, or
+ * non-empty stderr
+ */
+ private static byte[] execSearchPolicy(String... args)
+ throws InterruptedException, IOException {
+ File tmpDir = Files.createTempDirectory("searchpolicy").toFile();
+ try {
+ String[] envp;
+ File libsepolwrap;
+ if (isMac()) {
+ libsepolwrap = copyResourceToTempFile("/libsepolwrap.dylib");
+ libsepolwrap =
+ Files.move(
+ libsepolwrap.toPath(),
+ new File(tmpDir, "libsepolwrap.dylib").toPath()).toFile();
+ File libcpp = copyResourceToTempFile("/libc++.dylib");
+ Files.move(libcpp.toPath(), new File(tmpDir, "libc++.dylib").toPath());
+ envp = new String[] {"DYLD_LIBRARY_PATH=" + tmpDir.getAbsolutePath()};
+ } else {
+ libsepolwrap = copyResourceToTempFile("/libsepolwrap.so");
+ libsepolwrap =
+ Files.move(
+ libsepolwrap.toPath(),
+ new File(tmpDir, "libsepolwrap.so").toPath()).toFile();
+ File libcpp = copyResourceToTempFile("/libc++.so");
+ Files.move(libcpp.toPath(), new File(tmpDir, "libc++.so").toPath());
+ envp = new String[] {"LD_LIBRARY_PATH=" + tmpDir.getAbsolutePath()};
+ }
+ File searchpolicy = copyResourceToTempFile("/searchpolicy");
+ searchpolicy =
+ Files.move(
+ searchpolicy.toPath(),
+ new File(tmpDir, "searchpolicy").toPath()).toFile();
+ searchpolicy.setExecutable(true);
+ libsepolwrap.setExecutable(true);
+ List<String> cmd = new ArrayList<>(3 + args.length);
+ cmd.add(searchpolicy.getPath());
+ cmd.add("--libpath");
+ cmd.add(libsepolwrap.getPath());
+ for (String arg : args) {
+ cmd.add(arg);
+ }
+ return execAndCaptureOutput(cmd.toArray(new String[0]), envp);
+ } finally {
+ // Delete tmpDir
+ File[] files = tmpDir.listFiles();
+ if (files == null) {
+ files = new File[0];
+ }
+ for (File f : files) {
+ f.delete();
+ }
+ tmpDir.delete();
+ }
+ }
+
+ /**
+ * Executes the provided command and returns the contents of standard output.
+ *
+ * @throws IOException if execution fails, returns a non-zero error code, or non-empty stderr
+ */
+ private static byte[] execAndCaptureOutput(String[] cmd, String[] envp)
+ throws InterruptedException, IOException {
+ // Start process, read its stdout and stderr in two corresponding background threads, wait
+ // for process to terminate, throw if stderr is not empty or if return code != 0.
+ final Process p = Runtime.getRuntime().exec(cmd, envp);
+ ExecutorService executorService = null;
+ try {
+ executorService = Executors.newFixedThreadPool(2);
+ Future<byte[]> stdoutContentsFuture =
+ executorService.submit(new DrainCallable(p.getInputStream()));
+ Future<byte[]> stderrContentsFuture =
+ executorService.submit(new DrainCallable(p.getErrorStream()));
+ int errorCode = p.waitFor();
+ byte[] stderrContents = stderrContentsFuture.get();
+ if ((errorCode != 0) || (stderrContents.length > 0)) {
+ throw new IOException(
+ cmd[0] + " failed with error code " + errorCode
+ + ": " + new String(stderrContents));
+ }
+ return stdoutContentsFuture.get();
+ } catch (ExecutionException e) {
+ throw new IOException("Failed to read stdout or stderr of " + cmd[0], e);
+ } finally {
+ if (executorService != null) {
+ executorService.shutdownNow();
+ }
+ }
+ }
+
+ private static class DrainCallable implements Callable<byte[]> {
+ private final InputStream mIn;
+
+ private DrainCallable(InputStream in) {
+ mIn = in;
+ }
+
+ @Override
+ public byte[] call() throws IOException {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ byte[] buf = new byte[16384];
+ int chunkSize;
+ while ((chunkSize = mIn.read(buf)) != -1) {
+ result.write(buf, 0, chunkSize);
+ }
+ return result.toByteArray();
+ }
+ }
}
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index dca5076..c005749 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -108,8 +108,8 @@
mDevice = device;
}
- private File copyResourceToTempFile(String resName) throws IOException {
- InputStream is = this.getClass().getResourceAsStream(resName);
+ public static File copyResourceToTempFile(String resName) throws IOException {
+ InputStream is = SELinuxHostTest.class.getResourceAsStream(resName);
File tempFile = File.createTempFile("SELinuxHostTest", ".tmp");
FileOutputStream os = new FileOutputStream(tempFile);
byte[] buf = new byte[1024];
@@ -657,7 +657,7 @@
+ errorString, errorString.length() == 0);
}
- private boolean isMac() {
+ public static boolean isMac() {
String os = System.getProperty("os.name").toLowerCase();
return (os.startsWith("mac") || os.startsWith("darwin"));
}
diff --git a/hostsidetests/services/activityandwindowmanager/Android.mk b/hostsidetests/services/activityandwindowmanager/Android.mk
deleted file mode 100644
index 178cb8a..0000000
--- a/hostsidetests/services/activityandwindowmanager/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-include $(call all-subdir-makefiles)
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/Android.mk b/hostsidetests/services/activityandwindowmanager/activitymanager/Android.mk
deleted file mode 100644
index 1761ba6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-# Must match the package name in CtsTestCaseList.mk
-LOCAL_MODULE := CtsServicesHostTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
-LOCAL_STATIC_JAVA_LIBRARIES := cts-amwm-util \
- cts-display-service-app-util \
- platform-test-annotations-host
-
-LOCAL_CTS_TEST_PACKAGE := android.server
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_HOST_JAVA_LIBRARY)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
deleted file mode 100644
index 5ecde6b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Config for CTS Sample host test cases">
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsDeviceServicesTestApp.apk" />
- <option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
- <option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
- <option name="test-file-name" value="CtsDeviceDebuggableApp.apk" />
- <option name="test-file-name" value="CtsDeviceDisplaySizeApp.apk" />
- <option name="test-file-name" value="CtsDisplayServiceApp.apk" />
- <option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
- <option name="test-file-name" value="CtsDeviceTranslucentTestApp26.apk" />
- </target_preparer>
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="CtsServicesHostTestCases.jar" />
- <option name="runtime-hint" value="4m44s" />
- </test>
-</configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
deleted file mode 100755
index 63f41d1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/AndroidManifest.xml
+++ /dev/null
@@ -1,413 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.server.cts">
-
- <!-- virtual display test permissions -->
- <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
- <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
-
- <application>
- <activity android:name=".TestActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:exported="true"
- />
- <activity android:name=".TestActivityWithSameAffinity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:exported="true"
- android:taskAffinity="nobody.but.PipActivitySameAffinity"
- />
- <activity android:name=".TranslucentTestActivity"
- android:resizeableActivity="true"
- android:supportsPictureInPicture="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:theme="@style/Theme.Transparent" />
- <activity android:name=".VrTestActivity"
- android:resizeableActivity="true"
- android:exported="true"
- />
- <activity android:name=".ResumeWhilePausingActivity"
- android:allowEmbedded="true"
- android:resumeWhilePausing="true"
- android:taskAffinity=""
- android:exported="true"
- />
- <activity android:name=".ResizeableActivity"
- android:resizeableActivity="true"
- android:allowEmbedded="true"
- android:exported="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
- />
- <activity android:name=".NonResizeableActivity"
- android:resizeableActivity="false"
- android:exported="true"
- />
- <activity android:name=".DockedActivity"
- android:resizeableActivity="true"
- android:exported="true"
- android:taskAffinity="nobody.but.DockedActivity"
- />
- <activity android:name=".TranslucentActivity"
- android:theme="@android:style/Theme.Translucent.NoTitleBar"
- android:resizeableActivity="true"
- android:taskAffinity="nobody.but.TranslucentActivity"
- android:exported="true"
- />
- <activity android:name=".DialogWhenLargeActivity"
- android:exported="true"
- android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"
- />
- <activity android:name=".NoRelaunchActivity"
- android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
- android:exported="true"
- android:taskAffinity="nobody.but.NoRelaunchActivity"
- />
- <activity android:name=".SlowCreateActivity"
- android:resizeableActivity="true"
- android:exported="true"
- />
- <activity android:name=".LaunchingActivity"
- android:resizeableActivity="true"
- android:exported="true"
- android:taskAffinity="nobody.but.LaunchingActivity"
- />
- <activity android:name=".AltLaunchingActivity"
- android:resizeableActivity="true"
- android:exported="true"
- android:taskAffinity="nobody.but.LaunchingActivity"
- />
- <activity android:name=".PipActivity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- android:taskAffinity="nobody.but.PipActivity"
- />
- <activity android:name=".PipActivity2"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- android:taskAffinity="nobody.but.PipActivity2"
- />
- <activity android:name=".PipOnStopActivity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- android:taskAffinity="nobody.but.PipOnStopActivity"
- />
- <activity android:name=".PipActivityWithSameAffinity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- android:taskAffinity="nobody.but.PipActivitySameAffinity"
- />
- <activity android:name=".AlwaysFocusablePipActivity"
- android:theme="@style/Theme.Transparent"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- androidprv:alwaysFocusable="true"
- android:exported="true"
- android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
- />
- <activity android:name=".LaunchIntoPinnedStackPipActivity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- androidprv:alwaysFocusable="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- />
- <activity android:name=".LaunchPipOnPipActivity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- />
- <activity android:name=".LaunchEnterPipActivity"
- android:resizeableActivity="false"
- android:supportsPictureInPicture="true"
- androidprv:alwaysFocusable="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true"
- />
- <activity android:name=".FreeformActivity"
- android:resizeableActivity="true"
- android:taskAffinity="nobody.but.FreeformActivity"
- android:exported="true"
- />
- <activity android:name=".TopLeftLayoutActivity"
- android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true">
- <layout android:defaultWidth="240dp"
- android:defaultHeight="160dp"
- android:gravity="top|left"
- android:minWidth="100dp"
- android:minHeight="80dp"
- />
- </activity>
- <activity android:name=".TopRightLayoutActivity"
- android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true">
- <layout android:defaultWidth="25%"
- android:defaultHeight="35%"
- android:gravity="top|right"
- android:minWidth="90dp"
- android:minHeight="80dp"
- />
- </activity>
- <activity android:name=".BottomLeftLayoutActivity"
- android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true">
- <layout android:defaultWidth="25%"
- android:defaultHeight="35%"
- android:gravity="bottom|left"
- android:minWidth="90dp"
- android:minHeight="80dp"
- />
- </activity>
- <activity android:name=".BottomRightLayoutActivity"
- android:resizeableActivity="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="true">
- <layout android:defaultWidth="240dp"
- android:defaultHeight="160dp"
- android:gravity="bottom|right"
- android:minWidth="100dp"
- android:minHeight="80dp"
- />
- </activity>
- <activity android:name=".TurnScreenOnActivity"
- android:exported="true"
- />
- <activity android:name=".TurnScreenOnDismissKeyguardActivity"
- android:exported="true"
- />
- <activity android:name=".SingleTaskActivity"
- android:exported="true"
- android:launchMode="singleTask"
- />
- <activity android:name=".SingleInstanceActivity"
- android:exported="true"
- android:launchMode="singleInstance"
- />
- <activity android:name=".TrampolineActivity"
- android:exported="true"
- android:theme="@android:style/Theme.NoDisplay"
- />
- <activity android:name=".BroadcastReceiverActivity"
- android:resizeableActivity="true"
- android:exported="true"
- />
- <activity-alias android:enabled="true"
- android:exported="true"
- android:name=".EntryPointAliasActivity"
- android:targetActivity=".TrampolineActivity" >
- </activity-alias>
- <activity android:name=".BottomActivity"
- android:exported="true"
- android:theme="@style/NoPreview"
- />
- <activity android:name=".TopActivity"
- android:process=".top_process"
- android:exported="true"
- android:theme="@style/NoPreview"
- />
- <activity android:name=".TranslucentTopActivity"
- android:process=".top_process"
- android:exported="true"
- android:theme="@style/TranslucentTheme"
- />
- <!-- An animation test with an explicitly opaque theme, overriding device defaults, as the
- animation background being tested is not used in translucent activities. -->
- <activity android:name=".AnimationTestActivity"
- android:theme="@style/OpaqueTheme"
- android:exported="true"
- />
- <activity android:name=".VirtualDisplayActivity"
- android:resizeableActivity="true"
- android:exported="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- />
- <activity android:name=".ShowWhenLockedActivity"
- android:exported="true"
- />
- <activity android:name=".ShowWhenLockedWithDialogActivity"
- android:exported="true"
- />
- <activity android:name=".ShowWhenLockedDialogActivity"
- android:exported="true"
- android:theme="@android:style/Theme.Material.Dialog"
- />
- <activity android:name=".ShowWhenLockedTranslucentActivity"
- android:exported="true"
- android:theme="@android:style/Theme.Translucent"
- />
- <activity android:name=".DismissKeyguardActivity"
- android:exported="true"
- />
- <activity android:name=".DismissKeyguardMethodActivity"
- android:exported="true"
- />
- <activity android:name=".WallpaperActivity"
- android:exported="true"
- android:theme="@style/WallpaperTheme"
- />
- <activity android:name=".KeyguardLockActivity"
- android:exported="true"
- />
- <activity android:name=".LogConfigurationActivity"
- android:exported="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- />
- <activity android:name=".PortraitOrientationActivity"
- android:exported="true"
- android:screenOrientation="portrait"
- android:documentLaunchMode="always"
- />
- <activity android:name=".LandscapeOrientationActivity"
- android:exported="true"
- android:screenOrientation="landscape"
- android:documentLaunchMode="always"
- />
- <activity android:name=".MoveTaskToBackActivity"
- android:exported="true"
- android:launchMode="singleInstance"
- />
- <activity android:name=".FinishableActivity"
- android:exported="true"
- />
- <activity android:name=".NightModeActivity"
- android:exported="true"
- android:configChanges="uiMode"
- />
- <activity android:name=".FontScaleActivity"
- android:exported="true"
- />
- <activity android:name=".FontScaleNoRelaunchActivity"
- android:exported="true"
- android:configChanges="fontScale"
- />
- <receiver
- android:name=".LaunchBroadcastReceiver"
- android:enabled="true"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.server.cts.LAUNCH_BROADCAST_ACTION"/>
- </intent-filter>
- </receiver>
-
- <activity android:name=".AssistantActivity"
- android:exported="true" />
- <activity android:name=".TranslucentAssistantActivity"
- android:exported="true"
- android:theme="@style/Theme.Transparent" />
- <activity android:name=".LaunchAssistantActivityFromSession"
- android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
- android:exported="true" />
- <activity android:name=".LaunchAssistantActivityIntoAssistantStack"
- android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
- android:exported="true" />
-
- <service android:name=".AssistantVoiceInteractionService"
- android:permission="android.permission.BIND_VOICE_INTERACTION"
- android:exported="true">
- <meta-data android:name="android.voice_interaction"
- android:resource="@xml/interaction_service" />
- <intent-filter>
- <action android:name="android.service.voice.VoiceInteractionService" />
- </intent-filter>
- </service>
-
- <service android:name=".AssistantVoiceInteractionSessionService"
- android:permission="android.permission.BIND_VOICE_INTERACTION"
- android:exported="true" />
-
- <activity android:name=".SplashscreenActivity"
- android:taskAffinity="nobody.but.SplashscreenActivity"
- android:theme="@style/SplashscreenTheme"
- android:exported="true" />
-
-
- <activity android:name=".SwipeRefreshActivity"
- android:exported="true" />
-
- <activity android:name=".NoHistoryActivity"
- android:noHistory="true"
- android:exported="true" />
-
- <activity android:name=".ShowWhenLockedAttrActivity"
- android:showWhenLocked="true"
- android:exported="true" />
-
- <activity android:name=".ShowWhenLockedAttrRemoveAttrActivity"
- android:showWhenLocked="true"
- android:exported="true" />
-
- <activity android:name=".ShowWhenLockedAttrWithDialogActivity"
- android:showWhenLocked="true"
- android:exported="true" />
-
- <activity android:name=".TurnScreenOnAttrActivity"
- android:turnScreenOn="true"
- android:exported="true" />
-
- <activity android:name=".TurnScreenOnShowOnLockActivity"
- android:showWhenLocked="true"
- android:turnScreenOn="true"
- android:exported="true" />
-
- <activity android:name=".TurnScreenOnAttrRemoveAttrActivity"
- android:turnScreenOn="true"
- android:showWhenLocked="true"
- android:exported="true" />
-
- <activity android:name=".TurnScreenOnSingleTaskActivity"
- android:turnScreenOn="true"
- android:showWhenLocked="true"
- android:exported="true"
- android:launchMode="singleTask" />
-
- <activity android:name=".TurnScreenOnAttrDismissKeyguardActivity"
- android:turnScreenOn="true"
- android:exported="true"/>
-
- <activity android:name=".TurnScreenOnWithRelayoutActivity"
- android:exported="true"/>
-
- <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
- android:exported="true"
- android:enabled="true"
- android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
- <intent-filter>
- <action android:name="android.service.vr.VrListenerService" />
- </intent-filter>
- </service>
- </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/resizeable_activity.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/resizeable_activity.xml
deleted file mode 100644
index de52e61..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/resizeable_activity.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<android.server.cts.LifecycleLogView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-</android.server.cts.LifecycleLogView>
\ No newline at end of file
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/xml/interaction_service.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/xml/interaction_service.xml
deleted file mode 100644
index 7cf92a0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/xml/interaction_service.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:sessionService="android.server.cts.AssistantVoiceInteractionSessionService"
- android:recognitionService="android.server.cts.AssistantVoiceInteractionSessionService"
- android:supportsAssist="true" />
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AbstractLifecycleLogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AbstractLifecycleLogActivity.java
deleted file mode 100644
index 9d29917..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AbstractLifecycleLogActivity.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
-
-public abstract class AbstractLifecycleLogActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- Log.i(getTag(), "onCreate");
- }
-
- @Override
- protected void onStart() {
- super.onResume();
- Log.i(getTag(), "onStart");
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.i(getTag(), "onResume");
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- Log.i(getTag(), "onConfigurationChanged");
- }
-
- @Override
- public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
- super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
- Log.i(getTag(), "onMultiWindowModeChanged");
- }
-
- @Override
- public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
- Configuration newConfig) {
- super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
- Log.i(getTag(), "onPictureInPictureModeChanged");
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- Log.i(getTag(), "onPause");
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- Log.i(getTag(), "onStop");
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- Log.i(getTag(), "onDestroy");
- }
-
- protected abstract String getTag();
-
- protected void dumpConfiguration(Configuration config) {
- Log.i(getTag(), "Configuration: " + config);
- }
-
- protected void dumpDisplaySize(Configuration config) {
- // Dump the display size as seen by this Activity.
- final WindowManager wm = getSystemService(WindowManager.class);
- final Display display = wm.getDefaultDisplay();
- final Point point = new Point();
- display.getSize(point);
- final DisplayMetrics metrics = getResources().getDisplayMetrics();
-
- final String line = "config"
- + " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp)
- + " displaySize=" + buildCoordString(point.x, point.y)
- + " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels)
- + " smallestScreenWidth=" + config.smallestScreenWidthDp
- + " densityDpi=" + config.densityDpi
- + " orientation=" + config.orientation;
-
- Log.i(getTag(), line);
- }
-
- protected static String buildCoordString(int x, int y) {
- return "(" + x + "," + y + ")";
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AltLaunchingActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AltLaunchingActivity.java
deleted file mode 100644
index 7bf847e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AltLaunchingActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * An additional launching activity used for alternating between two activities.
- */
-public class AltLaunchingActivity extends LaunchingActivity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AlwaysFocusablePipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AlwaysFocusablePipActivity.java
deleted file mode 100644
index b6c0667..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AlwaysFocusablePipActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-
-public class AlwaysFocusablePipActivity extends Activity {
-
- static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
- final Intent intent = new Intent(caller, AlwaysFocusablePipActivity.class);
-
- intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK);
- if (newTask) {
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- }
-
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchBounds(new Rect(0, 0, 500, 500));
- options.setLaunchStackId(4 /* ActivityManager.StackId.PINNED_STACK_ID */);
- caller.startActivity(intent, options.toBundle());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AnimationTestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AnimationTestActivity.java
deleted file mode 100644
index 5ae923e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AnimationTestActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class AnimationTestActivity extends Activity {
-
- @Override
- protected void onResume() {
- super.onResume();
- overridePendingTransition(R.anim.animation_with_background, -1);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantActivity.java
deleted file mode 100644
index 18f290f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantActivity.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class AssistantActivity extends Activity {
-
- // Launches the given activity in onResume
- public static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
- // Finishes this activity in onResume, this happens after EXTRA_LAUNCH_NEW_TASK
- public static final String EXTRA_FINISH_SELF = "finish_self";
- // Attempts to enter picture-in-picture in onResume
- public static final String EXTRA_ENTER_PIP = "enter_pip";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Set the layout
- setContentView(R.layout.assistant);
-
- // Launch the new activity if requested
- if (getIntent().hasExtra(EXTRA_LAUNCH_NEW_TASK)) {
- Intent i = new Intent();
- i.setComponent(new ComponentName(this, getPackageName() + "."
- + getIntent().getStringExtra(EXTRA_LAUNCH_NEW_TASK)));
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(i);
- }
-
- // Enter pip if requested
- if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
- try {
- enterPictureInPictureMode();
- } catch (IllegalStateException e) {
- finish();
- return;
- }
- }
-
- // Finish this activity if requested
- if (getIntent().hasExtra(EXTRA_FINISH_SELF)) {
- finish();
- }
- }
-
- /**
- * Launches a new instance of the AssistantActivity directly into the assistant stack.
- */
- static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
- final Intent intent = new Intent(caller, AssistantActivity.class);
- intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
- if (extras != null) {
- intent.putExtras(extras);
- }
-
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(6 /* ActivityManager.StackId.ASSISTANT_STACK_ID */);
- caller.startActivity(intent, options.toBundle());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionService.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionService.java
deleted file mode 100644
index 51c2348..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionService.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.service.voice.VoiceInteractionService;
-import android.util.Log;
-
-public class AssistantVoiceInteractionService extends VoiceInteractionService {
-
- private static final String TAG = AssistantVoiceInteractionService.class.getSimpleName();
-
- private boolean mReady;
-
- @Override
- public void onReady() {
- super.onReady();
- mReady = true;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (!isActiveService(this, new ComponentName(this, getClass()))) {
- Log.wtf(TAG, "**** Not starting AssistantVoiceInteractionService because" +
- " it is not set as the current voice interaction service");
- stopSelf();
- return START_NOT_STICKY;
- }
- if (mReady) {
- Bundle extras = intent.getExtras() != null ? intent.getExtras() : new Bundle();
- showSession(extras, 0);
- }
- return START_NOT_STICKY;
- }
-
- /**
- * Starts the assistant voice interaction service, which initiates a new session that starts
- * the assistant activity.
- */
- public static void launchAssistantActivity(Context context, Bundle extras) {
- Intent i = new Intent(context, AssistantVoiceInteractionService.class);
- if (extras != null) {
- i.putExtras(extras);
- }
- context.startService(i);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionSessionService.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionSessionService.java
deleted file mode 100644
index e711ac4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/AssistantVoiceInteractionSessionService.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.service.voice.VoiceInteractionSession;
-import android.service.voice.VoiceInteractionSessionService;
-
-public class AssistantVoiceInteractionSessionService extends VoiceInteractionSessionService {
-
- @Override
- public VoiceInteractionSession onNewSession(Bundle args) {
- return new VoiceInteractionSession(this) {
- @Override
- public void onPrepareShow(Bundle args, int showFlags) {
- setUiEnabled(false);
- }
-
- @Override
- public void onShow(Bundle args, int showFlags) {
- Intent i = new Intent(AssistantVoiceInteractionSessionService.this,
- AssistantActivity.class);
- if (args != null) {
- i.putExtras(args);
- }
- startAssistantActivity(i);
- }
- };
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomActivity.java
deleted file mode 100644
index 38a71f1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomActivity.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-
-public class BottomActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = BottomActivity.class.getSimpleName();
-
- private int mStopDelay;
- private View mFloatingWindow;
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
- if (useWallpaper) {
- setTheme(R.style.WallpaperTheme);
- }
- setContentView(R.layout.main);
-
- // Delayed stop is for simulating a case where resume happens before
- // activityStopped() is received by AM, and the transition starts without
- // going through fully stopped state (see b/30255354).
- // If enabled, we stall onStop() of BottomActivity, open TopActivity but make
- // it finish before onStop() ends. This will cause BottomActivity to resume before
- // it notifies AM of activityStopped(). We also add a second window of
- // TYPE_BASE_APPLICATION, so that the transition animation could start earlier.
- // Otherwise the main window has to relayout to visible first and the error won't occur.
- // Note that if the test fails, we shouldn't try to change the app here to make
- // it pass. The test app is artificially made to simulate an failure case, but
- // it's not doing anything wrong.
- mStopDelay = getIntent().getIntExtra("STOP_DELAY", 0);
- if (mStopDelay > 0) {
- LayoutInflater inflater = getLayoutInflater();
- mFloatingWindow = inflater.inflate(R.layout.floating, null);
-
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- params.width = WindowManager.LayoutParams.WRAP_CONTENT;
- params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
- params.setTitle("Floating");
- getWindowManager().addView(mFloatingWindow, params);
- }
- }
-
- @Override
- public void onResume() {
- Log.d(TAG, "onResume() E");
- super.onResume();
-
- if (mStopDelay > 0) {
- // Refresh floating window
- Log.d(TAG, "Scheuling invalidate Floating Window in onResume()");
- mFloatingWindow.invalidate();
- }
-
- Log.d(TAG, "onResume() X");
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- if (mStopDelay > 0) {
- try {
- Log.d(TAG, "Stalling onStop() by " + mStopDelay + " ms...");
- Thread.sleep(mStopDelay);
- } catch(InterruptedException e) {}
-
- // Refresh floating window
- Log.d(TAG, "Scheuling invalidate Floating Window in onStop()");
- mFloatingWindow.invalidate();
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomLeftLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomLeftLayoutActivity.java
deleted file mode 100644
index 8de1cda..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomLeftLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class BottomLeftLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomRightLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomRightLayoutActivity.java
deleted file mode 100644
index 24fa2fc..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BottomRightLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class BottomRightLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
deleted file mode 100644
index 24be43a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/BroadcastReceiverActivity.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.server.cts.tools.ActivityLauncher;
-import android.util.Log;
-
-/**
- * Activity that registers broadcast receiver .
- */
-public class BroadcastReceiverActivity extends Activity {
-
- public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
- private static final String TAG = BroadcastReceiverActivity.class.getSimpleName();
-
- private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
-
- registerReceiver(mBroadcastReceiver, broadcastFilter);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- unregisterReceiver(mBroadcastReceiver);
- }
-
- public class TestBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Bundle extras = intent.getExtras();
- Log.i(TAG, "onReceive: extras=" + extras);
-
- if (extras == null) {
- return;
- }
- if (extras.getBoolean("finish")) {
- finish();
- }
- if (extras.getBoolean("moveToBack")) {
- moveTaskToBack(true);
- }
- if (extras.containsKey("orientation")) {
- setRequestedOrientation(extras.getInt("orientation"));
- }
- if (extras.getBoolean("dismissKeyguard")) {
- getWindow().addFlags(FLAG_DISMISS_KEYGUARD);
- }
- if (extras.getBoolean("dismissKeyguardMethod")) {
- getSystemService(KeyguardManager.class).requestDismissKeyguard(
- BroadcastReceiverActivity.this, new KeyguardDismissLoggerCallback(context));
- }
-
- ActivityLauncher.launchActivityFromExtras(BroadcastReceiverActivity.this, extras);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DialogWhenLargeActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DialogWhenLargeActivity.java
deleted file mode 100644
index 139c648..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DialogWhenLargeActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * Activity with DialogWhenLarge Theme.
- */
-public class DialogWhenLargeActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = DialogWhenLargeActivity.class.getSimpleName();
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardActivity.java
deleted file mode 100644
index 726a756..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class DismissKeyguardActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardMethodActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardMethodActivity.java
deleted file mode 100644
index c833eb2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DismissKeyguardMethodActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Bundle;
-
-public class DismissKeyguardMethodActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
- new KeyguardDismissLoggerCallback(this));
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DockedActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DockedActivity.java
deleted file mode 100644
index 007df5f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/DockedActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class DockedActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FinishableActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FinishableActivity.java
deleted file mode 100644
index d64b930..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FinishableActivity.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.BroadcastReceiver;
-import android.os.Bundle;
-
-/**
- * This activity finishes when you send a broadcast with the following action from adb shell
- * am broadcast -a 'android.server.cts.FinishableActivity.finish'
- */
-public class FinishableActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = FinishableActivity.class.getSimpleName();
- private static final String ACTION_FINISH = "android.server.cts.FinishableActivity.finish";
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null && intent.getAction().equals(ACTION_FINISH)) {
- finish();
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- IntentFilter intentFilter = new IntentFilter(ACTION_FINISH);
- registerReceiver(mReceiver, intentFilter);
- }
-
- @Override
- protected void onDestroy() {
- unregisterReceiver(mReceiver);
- super.onDestroy();
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleActivity.java
deleted file mode 100644
index a5620c1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleActivity.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package android.server.cts;
-
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-public class FontScaleActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = FontScaleActivity.class.getSimpleName();
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- dumpFontSize();
- }
-
- // We're basically ensuring that no matter what happens to the resources underneath the
- // Activity, any TypedArrays obtained from the pool have the correct DisplayMetrics.
- protected void dumpFontSize() {
- try (XmlResourceParser parser = getResources().getXml(R.layout.font_scale)) {
- //noinspection StatementWithEmptyBody
- while (parser.next() != XmlPullParser.START_TAG) { }
-
- final AttributeSet attrs = Xml.asAttributeSet(parser);
- TypedArray ta = getTheme().obtainStyledAttributes(attrs,
- new int[] { android.R.attr.textSize }, 0, 0);
- try {
- final int fontPixelSize = ta.getDimensionPixelSize(0, -1);
- if (fontPixelSize == -1) {
- throw new AssertionError("android:attr/textSize not found");
- }
-
- Log.i(getTag(), "fontPixelSize=" + fontPixelSize);
- } finally {
- ta.recycle();
- }
- } catch (XmlPullParserException | IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleNoRelaunchActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleNoRelaunchActivity.java
deleted file mode 100644
index 8789f68..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FontScaleNoRelaunchActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-package android.server.cts;
-
-import android.content.res.Configuration;
-
-public class FontScaleNoRelaunchActivity extends FontScaleActivity {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- dumpFontSize();
- }
-
- @Override
- protected String getTag() {
- return FontScaleNoRelaunchActivity.class.getSimpleName();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FreeformActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FreeformActivity.java
deleted file mode 100644
index f8c6d0c..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/FreeformActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.graphics.Rect;
-
-public class FreeformActivity extends Activity {
-
- @Override
- public void onResume() {
- super.onResume();
- final Intent intent = new Intent(this, TestActivity.class);
- intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchBounds(new Rect(0, 0, 900, 900));
- this.startActivity(intent, options.toBundle());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardDismissLoggerCallback.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardDismissLoggerCallback.java
deleted file mode 100644
index 860f2ae..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardDismissLoggerCallback.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.KeyguardManager;
-import android.app.KeyguardManager.KeyguardDismissCallback;
-import android.content.Context;
-import android.util.Log;
-
-public class KeyguardDismissLoggerCallback extends KeyguardDismissCallback {
-
- private final String TAG = "KeyguardDismissLoggerCallback";
-
- private final Context mContext;
-
- public KeyguardDismissLoggerCallback(Context context) {
- mContext = context;
- }
-
- @Override
- public void onDismissError() {
- Log.i(TAG, "onDismissError");
- }
-
- @Override
- public void onDismissSucceeded() {
- if (mContext.getSystemService(KeyguardManager.class).isDeviceLocked()) {
- // Device is still locked? What a fail. Don't print "onDismissSucceded" such that the
- // log fails.
- Log.i(TAG, "dismiss succedded was called but device is still locked.");
- } else {
- Log.i(TAG, "onDismissSucceeded");
- }
- }
-
- @Override
- public void onDismissCancelled() {
- Log.i(TAG, "onDismissCancelled");
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardLockActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardLockActivity.java
deleted file mode 100644
index 352cf04..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/KeyguardLockActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.KeyguardManager;
-import android.os.Bundle;
-
-public class KeyguardLockActivity extends BroadcastReceiverActivity {
-
- private KeyguardManager.KeyguardLock mKeyguardLock;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mKeyguardLock = getSystemService(KeyguardManager.class).newKeyguardLock("test");
- mKeyguardLock.disableKeyguard();
- }
-
- @Override
- protected void onDestroy() {
- mKeyguardLock.reenableKeyguard();
- super.onDestroy();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LandscapeOrientationActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LandscapeOrientationActivity.java
deleted file mode 100644
index ffd5aee..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LandscapeOrientationActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.res.Configuration;
-
-public class LandscapeOrientationActivity extends AbstractLifecycleLogActivity {
- @Override
- protected String getTag() {
- return "LandscapeOrientationActivity";
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- final Configuration config = getResources().getConfiguration();
- dumpDisplaySize(config);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- dumpDisplaySize(newConfig);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityFromSession.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityFromSession.java
deleted file mode 100644
index 2d562ff..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityFromSession.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class LaunchAssistantActivityFromSession extends Activity {
- @Override
- protected void onResume() {
- super.onResume();
- AssistantVoiceInteractionService.launchAssistantActivity(this, getIntent().getExtras());
- finishAndRemoveTask();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityIntoAssistantStack.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityIntoAssistantStack.java
deleted file mode 100644
index 62839a4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchAssistantActivityIntoAssistantStack.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class LaunchAssistantActivityIntoAssistantStack extends Activity {
-
- // Launches the translucent assist activity
- public static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
-
- @Override
- protected void onResume() {
- super.onResume();
-
- if (getIntent().hasExtra(EXTRA_IS_TRANSLUCENT) &&
- Boolean.valueOf(getIntent().getStringExtra(EXTRA_IS_TRANSLUCENT))) {
- TranslucentAssistantActivity.launchActivityIntoAssistantStack(this,
- getIntent().getExtras());
- } else {
- AssistantActivity.launchActivityIntoAssistantStack(this, getIntent().getExtras());
- }
- finishAndRemoveTask();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchBroadcastReceiver.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchBroadcastReceiver.java
deleted file mode 100644
index 6e713ec..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchBroadcastReceiver.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.server.cts.tools.ActivityLauncher;
-import android.util.Log;
-
-/** Broadcast receiver that can launch activities. */
-public class LaunchBroadcastReceiver extends BroadcastReceiver {
- private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- try {
- ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
- } catch (SecurityException e) {
- Log.e(TAG, "SecurityException launching activity");
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
deleted file mode 100644
index e2b4786..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchEnterPipActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Bundle;
-
-public class LaunchEnterPipActivity extends Activity {
- @Override
- protected void onCreate(Bundle bundle) {
- super.onCreate(bundle);
- PipActivity.launchEnterPipActivity(this);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchIntoPinnedStackPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchIntoPinnedStackPipActivity.java
deleted file mode 100644
index 86c4834..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchIntoPinnedStackPipActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class LaunchIntoPinnedStackPipActivity extends Activity {
- @Override
- protected void onResume() {
- super.onResume();
- AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, true /* newTask */);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchPipOnPipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchPipOnPipActivity.java
deleted file mode 100644
index d0b47b0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchPipOnPipActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.pm.PackageManager;
-
-public class LaunchPipOnPipActivity extends Activity {
- @Override
- public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
- super.onPictureInPictureModeChanged(isInPictureInPictureMode);
- AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this,
- getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK));
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchingActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchingActivity.java
deleted file mode 100644
index b312c97..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LaunchingActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.server.cts.tools.ActivityLauncher;
-
-/**
- * Activity that launches another activities when new intent is received.
- */
-public class LaunchingActivity extends Activity {
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LifecycleLogView.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LifecycleLogView.java
deleted file mode 100644
index 66e0cf2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LifecycleLogView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-
-public class LifecycleLogView extends View {
- private final String TAG = "LifecycleLogView";
-
- public LifecycleLogView(Context context) {
- super(context);
- }
-
- public LifecycleLogView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
- {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- Log.i(TAG, "onConfigurationChanged");
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LogConfigurationActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LogConfigurationActivity.java
deleted file mode 100644
index 61eb78c..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/LogConfigurationActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.util.Log;
-
-/**
- * Activity that logs configuration changes.
- */
-public class LogConfigurationActivity extends Activity {
-
- private static final String TAG = "LogConfigurationActivity";
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- Log.i(TAG, "Configuration changed: " + newConfig.screenWidthDp + ","
- + newConfig.screenHeightDp);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
deleted file mode 100644
index 6c47fb7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/MoveTaskToBackActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * Activity that finishes itself using "moveTaskToBack".
- */
-public class MoveTaskToBackActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = MoveTaskToBackActivity.class.getSimpleName();
-
- private String mFinishPoint;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Intent intent = getIntent();
- mFinishPoint = intent.getExtras().getString("finish_point");
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- if (mFinishPoint.equals("on_pause")) {
- moveTaskToBack(true /* nonRoot */);
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- if (mFinishPoint.equals("on_stop")) {
- moveTaskToBack(true /* nonRoot */);
- }
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NightModeActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NightModeActivity.java
deleted file mode 100644
index e33141b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NightModeActivity.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.UiModeManager;
-import android.content.Context;
-import android.os.Bundle;
-
-/** Activity that changes UI mode on creation and handles corresponding configuration change. */
-public class NightModeActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = NightModeActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- UiModeManager uiManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE);
- // Switch the mode two times to make sure it is independent of the current setting.
- uiManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
- uiManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoHistoryActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoHistoryActivity.java
deleted file mode 100644
index 6a84602..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoHistoryActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * An activity that has the noHistory flag set.
- */
-public class NoHistoryActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = NoHistoryActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoRelaunchActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoRelaunchActivity.java
deleted file mode 100644
index 9cc3b9d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NoRelaunchActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-public class NoRelaunchActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = NoRelaunchActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NonResizeableActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NonResizeableActivity.java
deleted file mode 100644
index b871a8d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/NonResizeableActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-public class NonResizeableActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = NonResizeableActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
deleted file mode 100644
index f968970..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.app.PictureInPictureParams;
-import android.content.res.Configuration;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.Rational;
-import android.view.WindowManager;
-
-public class PipActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = "PipActivity";
-
- // Intent action that this activity dynamically registers to enter picture-in-picture
- private static final String ACTION_ENTER_PIP = "android.server.cts.PipActivity.enter_pip";
- // Intent action that this activity dynamically registers to move itself to the back
- private static final String ACTION_MOVE_TO_BACK = "android.server.cts.PipActivity.move_to_back";
- // Intent action that this activity dynamically registers to expand itself.
- // If EXTRA_SET_ASPECT_RATIO_WITH_DELAY is set, it will also attempt to apply the aspect ratio
- // after a short delay.
- private static final String ACTION_EXPAND_PIP = "android.server.cts.PipActivity.expand_pip";
- // Intent action that this activity dynamically registers to set requested orientation.
- // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
- private static final String ACTION_SET_REQUESTED_ORIENTATION =
- "android.server.cts.PipActivity.set_requested_orientation";
- // Intent action that will finish this activity
- private static final String ACTION_FINISH = "android.server.cts.PipActivity.finish";
-
- // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
- private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
- // Calls enterPictureInPicture() on creation
- private static final String EXTRA_ENTER_PIP = "enter_pip";
- // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
- private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
- "enter_pip_aspect_ratio_numerator";
- // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
- private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
- "enter_pip_aspect_ratio_denominator";
- // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
- private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
- // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
- private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
- // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
- // fixed delay
- private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
- "set_aspect_ratio_with_delay_numerator";
- // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
- // fixed delay
- private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
- "set_aspect_ratio_with_delay_denominator";
- // Adds a click listener to finish this activity when it is clicked
- private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
- // Calls requestAutoEnterPictureInPicture() with the value provided
- private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
- // Starts the activity (component name) provided by the value at the end of onCreate
- private static final String EXTRA_START_ACTIVITY = "start_activity";
- // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
- private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
- // Calls enterPictureInPicture() again after onPictureInPictureModeChanged(false) is called
- private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
- // Shows this activity over the keyguard
- private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
- // Adds an assertion that we do not ever get onStop() before we enter picture in picture
- private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
- // The amount to delay to artificially introduce in onPause() (before EXTRA_ENTER_PIP_ON_PAUSE
- // is processed)
- private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
-
- private boolean mEnteredPictureInPicture;
-
- private Handler mHandler = new Handler();
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null) {
- switch (intent.getAction()) {
- case ACTION_ENTER_PIP:
- enterPictureInPictureMode();
- break;
- case ACTION_MOVE_TO_BACK:
- moveTaskToBack(false /* nonRoot */);
- break;
- case ACTION_EXPAND_PIP:
- // Trigger the activity to expand
- Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
- startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- startActivity(startIntent);
-
- if (intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR)
- && intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR)) {
- // Ugly, but required to wait for the startActivity to actually start
- // the activity...
- mHandler.postDelayed(() -> {
- final PictureInPictureParams.Builder builder =
- new PictureInPictureParams.Builder();
- builder.setAspectRatio(getAspectRatio(intent,
- EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR,
- EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR));
- setPictureInPictureParams(builder.build());
- }, 100);
- }
- break;
- case ACTION_SET_REQUESTED_ORIENTATION:
- setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
- EXTRA_FIXED_ORIENTATION)));
- break;
- case ACTION_FINISH:
- finish();
- break;
- }
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Set the fixed orientation if requested
- if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
- final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
- setRequestedOrientation(ori);
- }
-
- // Set the window flag to show over the keyguard
- if (getIntent().hasExtra(EXTRA_SHOW_OVER_KEYGUARD)) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- }
-
- // Enter picture in picture with the given aspect ratio if provided
- if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
- if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
- && getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR)) {
- try {
- final PictureInPictureParams.Builder builder =
- new PictureInPictureParams.Builder();
- builder.setAspectRatio(getAspectRatio(getIntent(),
- EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
- EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
- enterPictureInPictureMode(builder.build());
- } catch (Exception e) {
- // This call can fail intentionally if the aspect ratio is too extreme
- }
- } else {
- enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
- }
- }
-
- // We need to wait for either enterPictureInPicture() or requestAutoEnterPictureInPicture()
- // to be called before setting the aspect ratio
- if (getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR)
- && getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR)) {
- final PictureInPictureParams.Builder builder =
- new PictureInPictureParams.Builder();
- builder.setAspectRatio(getAspectRatio(getIntent(),
- EXTRA_SET_ASPECT_RATIO_NUMERATOR, EXTRA_SET_ASPECT_RATIO_DENOMINATOR));
- try {
- setPictureInPictureParams(builder.build());
- } catch (Exception e) {
- // This call can fail intentionally if the aspect ratio is too extreme
- }
- }
-
- // Enable tap to finish if necessary
- if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
- setContentView(R.layout.tap_to_finish_pip_layout);
- findViewById(R.id.content).setOnClickListener(v -> {
- finish();
- });
- }
-
- // Launch a new activity if requested
- String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
- if (launchActivityComponent != null) {
- Intent launchIntent = new Intent();
- launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
- startActivity(launchIntent);
- }
-
- // Register the broadcast receiver
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_ENTER_PIP);
- filter.addAction(ACTION_MOVE_TO_BACK);
- filter.addAction(ACTION_EXPAND_PIP);
- filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
- filter.addAction(ACTION_FINISH);
- registerReceiver(mReceiver, filter);
-
- // Dump applied display metrics.
- Configuration config = getResources().getConfiguration();
- dumpDisplaySize(config);
- dumpConfiguration(config);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- // Finish self if requested
- if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
- finish();
- }
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- // Pause if requested
- if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
- SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
- }
-
- // Enter PIP on move to background
- if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
- enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
- Log.w(TAG, "Unexpected onStop() called before entering picture-in-picture");
- finish();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- unregisterReceiver(mReceiver);
- }
-
- @Override
- public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
- super.onPictureInPictureModeChanged(isInPictureInPictureMode);
-
- // Fail early if the activity state does not match the dispatched state
- if (isInPictureInPictureMode() != isInPictureInPictureMode) {
- Log.w(TAG, "Received onPictureInPictureModeChanged mode=" + isInPictureInPictureMode
- + " activityState=" + isInPictureInPictureMode());
- finish();
- }
-
- // Mark that we've entered picture-in-picture so that we can stop checking for
- // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
- if (isInPictureInPictureMode) {
- mEnteredPictureInPicture = true;
- }
-
- if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
- // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
- // checking that the stacks ever changed). Therefor, we need to delay here slightly to
- // allow the tests to verify that the stacks have changed before re-entering.
- mHandler.postDelayed(() -> {
- enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
- }, 1000);
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- dumpDisplaySize(newConfig);
- dumpConfiguration(newConfig);
- }
-
- /**
- * Launches a new instance of the PipActivity directly into the pinned stack.
- */
- static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
- final Intent intent = new Intent(caller, PipActivity.class);
- intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
-
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchBounds(bounds);
- options.setLaunchStackId(4 /* ActivityManager.StackId.PINNED_STACK_ID */);
- caller.startActivity(intent, options.toBundle());
- }
-
- /**
- * Launches a new instance of the PipActivity in the same task that will automatically enter
- * PiP.
- */
- static void launchEnterPipActivity(Activity caller) {
- final Intent intent = new Intent(caller, PipActivity.class);
- intent.putExtra(EXTRA_ENTER_PIP, "true");
- intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
- caller.startActivity(intent);
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- /**
- * @return a {@link Rational} aspect ratio from the given intent and extras.
- */
- private Rational getAspectRatio(Intent intent, String extraNum, String extraDenom) {
- return new Rational(
- Integer.valueOf(intent.getStringExtra(extraNum)),
- Integer.valueOf(intent.getStringExtra(extraDenom)));
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity2.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity2.java
deleted file mode 100644
index 53b4f75..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivity2.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * A secondary activity that has the same behavior as {@link PipActivity}.
- */
-public class PipActivity2 extends PipActivity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivityWithSameAffinity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivityWithSameAffinity.java
deleted file mode 100644
index e97dc0e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipActivityWithSameAffinity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.PictureInPictureParams;
-
-/**
- * An activity that can enter PiP with a fixed affinity to match
- * {@link TestActivityWithSameAffinity}.
- */
-public class PipActivityWithSameAffinity extends Activity {
-
- @Override
- protected void onResume() {
- super.onResume();
-
- enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipOnStopActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipOnStopActivity.java
deleted file mode 100644
index 1abc696..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PipOnStopActivity.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * This activity will try and enter picture in picture when it is stopped.
- */
-public class PipOnStopActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.tap_to_finish_pip_layout);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- startActivity(new Intent(this, PipActivity.class));
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- try {
- enterPictureInPictureMode();
- } catch (RuntimeException e) {
- // Known failure, we expect this call to throw an exception
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PortraitOrientationActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PortraitOrientationActivity.java
deleted file mode 100644
index 5fa1fc8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/PortraitOrientationActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.res.Configuration;
-
-public class PortraitOrientationActivity extends AbstractLifecycleLogActivity {
-
- @Override
- protected String getTag() {
- return "PortraitOrientationActivity";
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- final Configuration config = getResources().getConfiguration();
- dumpDisplaySize(config);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- dumpDisplaySize(newConfig);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResizeableActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResizeableActivity.java
deleted file mode 100644
index aad874b..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResizeableActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.server.cts;
-
-import android.content.res.Configuration;
-import android.os.Bundle;
-
-public class ResizeableActivity extends AbstractLifecycleLogActivity {
- @Override
- protected String getTag() {
- return "ResizeableActivity";
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.resizeable_activity);
- dumpDisplaySize(getResources().getConfiguration());
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- dumpDisplaySize(newConfig);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
deleted file mode 100644
index 578d7e5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ResumeWhilePausingActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class ResumeWhilePausingActivity extends Activity {
- // Empty
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedActivity.java
deleted file mode 100644
index 6e3348a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedActivity extends BroadcastReceiverActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrActivity.java
deleted file mode 100644
index c53c485..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class ShowWhenLockedAttrActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = ShowWhenLockedAttrActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrRemoveAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrRemoveAttrActivity.java
deleted file mode 100644
index dbad34d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrRemoveAttrActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.os.Bundle;
-
-public class ShowWhenLockedAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = ShowWhenLockedAttrRemoveAttrActivity.class.getSimpleName();
-
- @Override
- protected void onStop() {
- super.onStop();
- setShowWhenLocked(false);
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrWithDialogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrWithDialogActivity.java
deleted file mode 100644
index 136706a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedAttrWithDialogActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedAttrWithDialogActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- new AlertDialog.Builder(this)
- .setTitle("Dialog")
- .show();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedDialogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedDialogActivity.java
deleted file mode 100644
index 5b60139..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedDialogActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedDialogActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedTranslucentActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedTranslucentActivity.java
deleted file mode 100644
index 929c9f7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedTranslucentActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedTranslucentActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- setContentView(R.layout.translucent);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedWithDialogActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedWithDialogActivity.java
deleted file mode 100644
index 58d5ac7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/ShowWhenLockedWithDialogActivity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class ShowWhenLockedWithDialogActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- new AlertDialog.Builder(this)
- .setTitle("Dialog")
- .show();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleInstanceActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleInstanceActivity.java
deleted file mode 100644
index e0eaecf..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleInstanceActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class SingleInstanceActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleTaskActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleTaskActivity.java
deleted file mode 100644
index 7ffde13..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SingleTaskActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class SingleTaskActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SlowCreateActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SlowCreateActivity.java
deleted file mode 100644
index a757165..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SlowCreateActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SlowCreateActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- try {
- Thread.sleep(2000);
- } catch(InterruptedException e) {}
- super.onCreate(savedInstanceState);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SplashscreenActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SplashscreenActivity.java
deleted file mode 100644
index 22391bd..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SplashscreenActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-/**
- * Activity that shows a custom splashscreen when being launched.
- */
-public class SplashscreenActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // Make sure splash screen is visible. The test won't take 5 seconds because the condition
- // such that we can dump the state will trigger much earlier and then the test will just
- // kill us.
- SystemClock.sleep(5000);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshActivity.java
deleted file mode 100644
index 5f36abc..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshActivity.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.server.cts.SwipeRefreshLayout;
-
-
-/**
- * An activity containing a SwipeRefreshLayout which prevents activity idle.
- */
-public class SwipeRefreshActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = SwipeRefreshActivity.class.getSimpleName();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(new SwipeRefreshLayout(this));
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshLayout.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshLayout.java
deleted file mode 100644
index a061d9e..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/SwipeRefreshLayout.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * An extension of {@link android.support.v4.widget.SwipeRefreshLayout} that calls
- * {@link #setRefreshing} during construction, preventing activity idle.
- */
-public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout {
- public SwipeRefreshLayout(Context context) {
- super(context);
- initialize();
- }
-
- public SwipeRefreshLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
- }
-
- private void initialize() {
- setRefreshing(true);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
deleted file mode 100644
index f9a86c6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = TestActivity.class.getSimpleName();
-
- // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
- private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
-
- // Finishes the activity
- private static final String ACTION_FINISH_SELF = "android.server.cts.TestActivity.finish_self";
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null && intent.getAction().equals(ACTION_FINISH_SELF)) {
- finish();
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // Set the fixed orientation if requested
- if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
- final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
- setRequestedOrientation(ori);
- }
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- registerReceiver(mReceiver, new IntentFilter(ACTION_FINISH_SELF));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- final Configuration config = getResources().getConfiguration();
- dumpDisplaySize(config);
- dumpConfiguration(config);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- unregisterReceiver(mReceiver);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- dumpDisplaySize(newConfig);
- dumpConfiguration(newConfig);
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivityWithSameAffinity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivityWithSameAffinity.java
deleted file mode 100644
index db75dee..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivityWithSameAffinity.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.PictureInPictureParams;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestActivityWithSameAffinity extends TestActivity {
-
- private static final String TAG = TestActivityWithSameAffinity.class.getSimpleName();
-
- // Calls enterPictureInPicture() on creation
- private static final String EXTRA_ENTER_PIP = "enter_pip";
- // Starts the activity (component name) provided by the value at the end of onCreate
- private static final String EXTRA_START_ACTIVITY = "start_activity";
- // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
- private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // Enter picture in picture if requested
- if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
- enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
- }
-
- // Launch a new activity if requested
- String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
- if (launchActivityComponent != null) {
- Intent launchIntent = new Intent();
- launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
- startActivity(launchIntent);
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- // Finish self if requested
- if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
- finish();
- }
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopActivity.java
deleted file mode 100644
index 6684999..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Handler;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TopActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = TopActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
- if (useWallpaper) {
- setTheme(R.style.WallpaperTheme);
- }
-
- final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
- if (finishDelay > 0) {
- Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Calling finish()");
- finish();
- }
- }, finishDelay);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopLeftLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopLeftLayoutActivity.java
deleted file mode 100644
index 2305582..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopLeftLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class TopLeftLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopRightLayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopRightLayoutActivity.java
deleted file mode 100644
index 701d3db..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TopRightLayoutActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class TopRightLayoutActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TrampolineActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TrampolineActivity.java
deleted file mode 100644
index b3f9444..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TrampolineActivity.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Bundle;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.content.Intent;
-
-public class TrampolineActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = TrampolineActivity.class.getSimpleName();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // Add a delay here to expose more easily a failure case where the real target
- // activity is visible before it's launched, because its task is being brought
- // to foreground. We need to verify that 'am start' is unblocked correctly.
- try {
- Thread.sleep(2000);
- } catch(InterruptedException e) {}
- Intent intent = new Intent(this, SingleTaskActivity.class);
- intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK);
-
- startActivity(intent);
- finish();
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentActivity.java
deleted file mode 100644
index 96697aa..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class TranslucentActivity extends Activity {
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentAssistantActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentAssistantActivity.java
deleted file mode 100644
index e979712..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentAssistantActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class TranslucentAssistantActivity extends AssistantActivity {
-
- /**
- * Launches a new instance of the TranslucentAssistantActivity directly into the assistant
- * stack.
- */
- static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
- final Intent intent = new Intent(caller, TranslucentAssistantActivity.class);
- intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
- if (extras != null) {
- intent.putExtras(extras);
- }
-
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(6 /* ActivityManager.StackId.ASSISTANT_STACK_ID */);
- caller.startActivity(intent, options.toBundle());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTestActivity.java
deleted file mode 100644
index 1d10a51..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTestActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Bundle;
-
-public class TranslucentTestActivity extends TestActivity {
-
- private static final String TAG = TranslucentTestActivity.class.getSimpleName();
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- setContentView(R.layout.task_overlay);
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTopActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTopActivity.java
deleted file mode 100644
index 9d96898..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TranslucentTopActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.os.Handler;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TranslucentTopActivity extends AbstractLifecycleLogActivity {
-
- private static final String TAG = TranslucentTopActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
- if (useWallpaper) {
- setTheme(R.style.TranslucentWallpaperTheme);
- }
-
- final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
- if (finishDelay > 0) {
- Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Calling finish()");
- finish();
- }
- }, finishDelay);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnActivity.java
deleted file mode 100644
index 79e31b3..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class TurnScreenOnActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- super.onCreate(savedInstanceState);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrActivity.java
deleted file mode 100644
index 5e1f5d1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnAttrActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = TurnScreenOnAttrActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrDismissKeyguardActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrDismissKeyguardActivity.java
deleted file mode 100644
index b1b860f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrDismissKeyguardActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.KeyguardManager;
-import android.os.Bundle;
-
-public class TurnScreenOnAttrDismissKeyguardActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = TurnScreenOnAttrDismissKeyguardActivity.class.getSimpleName();
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- ((KeyguardManager) getSystemService(KEYGUARD_SERVICE))
- .requestDismissKeyguard(this, new KeyguardDismissLoggerCallback(this));
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrRemoveAttrActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrRemoveAttrActivity.java
deleted file mode 100644
index 29911fe..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnAttrRemoveAttrActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = TurnScreenOnAttrRemoveAttrActivity.class.getSimpleName();
-
- @Override
- protected void onStop() {
- super.onStop();
- setTurnScreenOn(false);
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnDismissKeyguardActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnDismissKeyguardActivity.java
deleted file mode 100644
index 487f785..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnDismissKeyguardActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class TurnScreenOnDismissKeyguardActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
- new KeyguardDismissLoggerCallback(this));
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnShowOnLockActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnShowOnLockActivity.java
deleted file mode 100644
index 57ff4fb..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnShowOnLockActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnShowOnLockActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = TurnScreenOnShowOnLockActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnSingleTaskActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnSingleTaskActivity.java
deleted file mode 100644
index 29c71d0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnSingleTaskActivity.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-public class TurnScreenOnSingleTaskActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = TurnScreenOnSingleTaskActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnWithRelayoutActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnWithRelayoutActivity.java
deleted file mode 100644
index 14c2801..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TurnScreenOnWithRelayoutActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-import android.view.WindowManager;
-
-public class TurnScreenOnWithRelayoutActivity extends AbstractLifecycleLogActivity {
- private static final String TAG = TurnScreenOnWithRelayoutActivity.class.getSimpleName();
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
-
- // This is to force a relayout, specifically with insets changed. When the insets are
- // changed, it will trigger a performShow that could turn the screen on.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VirtualDisplayActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VirtualDisplayActivity.java
deleted file mode 100644
index dd12acb..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VirtualDisplayActivity.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.os.Bundle;
-import android.server.cts.tools.ActivityLauncher;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-
-import java.util.HashMap;
-
-/**
- * Activity that is able to create and destroy a virtual display.
- */
-public class VirtualDisplayActivity extends Activity implements SurfaceHolder.Callback {
- private static final String TAG = "VirtualDisplayActivity";
-
- private static final int DEFAULT_DENSITY_DPI = 160;
- private static final String KEY_DENSITY_DPI = "density_dpi";
- private static final String KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD
- = "can_show_with_insecure_keyguard";
- private static final String KEY_PUBLIC_DISPLAY = "public_display";
- private static final String KEY_RESIZE_DISPLAY = "resize_display";
- private static final String KEY_LAUNCH_TARGET_ACTIVITY = "launch_target_activity";
- private static final String KEY_COUNT = "count";
-
- private DisplayManager mDisplayManager;
-
- // Container for details about a pending virtual display creation request.
- private static class VirtualDisplayRequest {
- public final SurfaceView surfaceView;
- public final Bundle extras;
-
- public VirtualDisplayRequest(SurfaceView surfaceView, Bundle extras) {
- this.surfaceView = surfaceView;
- this.extras = extras;
- }
- }
-
- // Container to hold association between an active virtual display and surface view.
- private static class VirtualDisplayEntry {
- public final VirtualDisplay display;
- public final SurfaceView surfaceView;
- public final boolean resizeDisplay;
- public final int density;
-
- public VirtualDisplayEntry(VirtualDisplay display, SurfaceView surfaceView, int density,
- boolean resizeDisplay) {
- this.display = display;
- this.surfaceView = surfaceView;
- this.density = density;
- this.resizeDisplay = resizeDisplay;
- }
- }
-
- private HashMap<Surface, VirtualDisplayRequest> mPendingDisplayRequests;
- private HashMap<Surface, VirtualDisplayEntry> mVirtualDisplays;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.virtual_display_layout);
-
- mVirtualDisplays = new HashMap<>();
- mPendingDisplayRequests = new HashMap<>();
- mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- final Bundle extras = intent.getExtras();
- if (extras == null) {
- return;
- }
-
- String command = extras.getString("command");
- switch (command) {
- case "create_display":
- createVirtualDisplay(extras);
- break;
- case "destroy_display":
- destroyVirtualDisplays();
- break;
- case "resize_display":
- resizeDisplay();
- break;
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- destroyVirtualDisplays();
- }
-
- private void createVirtualDisplay(Bundle extras) {
- final int requestedCount = extras.getInt(KEY_COUNT, 1);
- Log.d(TAG, "createVirtualDisplays. requested count:" + requestedCount);
-
- for (int displayCount = 0; displayCount < requestedCount; ++displayCount) {
- final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
- final SurfaceView surfaceView = new SurfaceView(this);
- surfaceView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- surfaceView.getHolder().addCallback(this);
- mPendingDisplayRequests.put(surfaceView.getHolder().getSurface(),
- new VirtualDisplayRequest(surfaceView, extras));
- root.addView(surfaceView);
- }
- }
-
- private void destroyVirtualDisplays() {
- Log.d(TAG, "destroyVirtualDisplays");
- final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
-
- for (VirtualDisplayEntry entry : mVirtualDisplays.values()) {
- Log.d(TAG, "destroying:" + entry.display);
- entry.display.release();
- root.removeView(entry.surfaceView);
- }
-
- mPendingDisplayRequests.clear();
- mVirtualDisplays.clear();
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder surfaceHolder) {
- final VirtualDisplayRequest entry =
- mPendingDisplayRequests.remove(surfaceHolder.getSurface());
-
- if (entry == null) {
- return;
- }
-
- final int densityDpi = entry.extras.getInt(KEY_DENSITY_DPI, DEFAULT_DENSITY_DPI);
- final boolean resizeDisplay = entry.extras.getBoolean(KEY_RESIZE_DISPLAY);
- final String launchActivityName = entry.extras.getString(KEY_LAUNCH_TARGET_ACTIVITY);
- final Surface surface = surfaceHolder.getSurface();
-
- // Initially, the surface will not have a set width or height so rely on the parent.
- // This should be accurate with match parent on both params.
- final int width = surfaceHolder.getSurfaceFrame().width();
- final int height = surfaceHolder.getSurfaceFrame().height();
-
- int flags = 0;
-
- final boolean canShowWithInsecureKeyguard
- = entry.extras.getBoolean(KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD);
- if (canShowWithInsecureKeyguard) {
- flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
- }
-
- final boolean publicDisplay = entry.extras.getBoolean(KEY_PUBLIC_DISPLAY);
- if (publicDisplay) {
- flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
- }
-
- Log.d(TAG, "createVirtualDisplay: " + width + "x" + height + ", dpi: "
- + densityDpi + ", canShowWithInsecureKeyguard=" + canShowWithInsecureKeyguard
- + ", publicDisplay=" + publicDisplay);
- try {
- VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
- "VirtualDisplay" + mVirtualDisplays.size(), width,
- height, densityDpi, surface, flags);
- mVirtualDisplays.put(surface,
- new VirtualDisplayEntry(virtualDisplay, entry.surfaceView, densityDpi,
- resizeDisplay));
- if (launchActivityName != null) {
- final int displayId = virtualDisplay.getDisplay().getDisplayId();
- Log.d(TAG, "Launch activity after display created: activityName="
- + launchActivityName + ", displayId=" + displayId);
- launchActivity(launchActivityName, displayId);
- }
- } catch (IllegalArgumentException e) {
- final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
- // This is expected when trying to create show-when-locked public display.
- root.removeView(entry.surfaceView);
- }
-
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
- final VirtualDisplayEntry entry = mVirtualDisplays.get(surfaceHolder.getSurface());
-
- if (entry != null && entry.resizeDisplay) {
- entry.display.resize(width, height, entry.density);
- }
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
- }
-
- /** Resize virtual display to half of the surface frame size. */
- private void resizeDisplay() {
- final VirtualDisplayEntry vd = (VirtualDisplayEntry) mVirtualDisplays.values().toArray()[0];
- final SurfaceHolder surfaceHolder = vd.surfaceView.getHolder();
- vd.display.resize(surfaceHolder.getSurfaceFrame().width() / 2,
- surfaceHolder.getSurfaceFrame().height() / 2, vd.density);
- }
-
- private void launchActivity(String activityName, int displayId) {
- final Bundle extras = new Bundle();
- extras.putBoolean("launch_activity", true);
- extras.putString("target_activity", activityName);
- extras.putInt("display_id", displayId);
- ActivityLauncher.launchActivityFromExtras(this, extras);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VrTestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VrTestActivity.java
deleted file mode 100644
index 6ef25e2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/VrTestActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.cts.verifier.vr.MockVrListenerService;
-
-/**
- * Activity that is able to create and destroy a virtual display.
- */
-public class VrTestActivity extends Activity {
- private static final String TAG = "VrTestActivity";
- private static final boolean DEBUG = false;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.i(TAG, "onCreate called.");
- super.onCreate(savedInstanceState);
- try {
- setVrModeEnabled(true, new ComponentName(this, MockVrListenerService.class));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Could not set VR mode: " + e);
- }
- }
-
- @Override
- protected void onResume() {
- if (DEBUG) Log.i(TAG, "onResume called.");
- super.onResume();
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- if (DEBUG) Log.i(TAG, "onWindowFocusChanged called with " + hasFocus);
- super.onWindowFocusChanged(hasFocus);
- }
-
- @Override
- protected void onPause() {
- if (DEBUG) Log.i(TAG, "onPause called.");
- super.onPause();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/WallpaperActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/WallpaperActivity.java
deleted file mode 100644
index 63e11d5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/WallpaperActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.app.Activity;
-
-public class WallpaperActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
deleted file mode 100644
index 15b55f5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/tools/ActivityLauncher.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.tools;
-
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
-
-import android.app.ActivityOptions;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.server.cts.TestActivity;
-import android.util.Log;
-
-/** Utility class which contains common code for launching activities. */
-public class ActivityLauncher {
- private static final String TAG = ActivityLauncher.class.getSimpleName();
-
- public static void launchActivityFromExtras(final Context context, Bundle extras) {
- if (extras == null || !extras.getBoolean("launch_activity")) {
- return;
- }
-
- Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
-
- final Intent newIntent = new Intent();
- final String targetActivity = extras.getString("target_activity");
- if (targetActivity != null) {
- final String extraPackageName = extras.getString("package_name");
- final String packageName = extraPackageName != null ? extraPackageName
- : context.getApplicationContext().getPackageName();
- newIntent.setComponent(new ComponentName(packageName,
- packageName + "." + targetActivity));
- } else {
- newIntent.setClass(context, TestActivity.class);
- }
-
- if (extras.getBoolean("launch_to_the_side")) {
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
- if (extras.getBoolean("random_data")) {
- final Uri data = new Uri.Builder()
- .path(String.valueOf(System.currentTimeMillis()))
- .build();
- newIntent.setData(data);
- }
- }
- if (extras.getBoolean("multiple_task")) {
- newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- }
- if (extras.getBoolean("new_task")) {
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- }
-
- if (extras.getBoolean("reorder_to_front")) {
- newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
- }
-
- ActivityOptions options = null;
- final int displayId = extras.getInt("display_id", -1);
- if (displayId != -1) {
- options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(displayId);
- newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
- }
-
- try {
- context.startActivity(newIntent, options != null ? options.toBundle() : null);
- } catch (SecurityException e) {
- Log.e(TAG, "SecurityException launching activity");
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml
deleted file mode 100644
index c00f2b6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.server.cts.debuggable">
-
- <!--
- * Security policy requires that only debuggable processes can be profiled
- * which is tested by ActivityManagerAmProfileTests.
- -->
- <application android:debuggable="true">
- <activity android:name=".DebuggableAppActivity"
- android:resizeableActivity="true"
- android:exported="true" />
- </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/debuggable/DebuggableAppActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/debuggable/DebuggableAppActivity.java
deleted file mode 100644
index 504ffcf..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/src/android/server/cts/debuggable/DebuggableAppActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.debuggable;
-
-import android.app.Activity;
-
-public class DebuggableAppActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/AndroidManifest.xml
deleted file mode 100644
index 7727759..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.displaysize.app">
-
- <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-
- <!-- Set a ridiculously high value for required smallest width. Hopefully
- we never have to run CTS on Times Square display. -->
- <supports-screens android:requiresSmallestWidthDp="100000" />
-
- <application>
- <activity android:name=".SmallestWidthActivity"
- android:exported="true" />
- </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/src/android/displaysize/app/SmallestWidthActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/src/android/displaysize/app/SmallestWidthActivity.java
deleted file mode 100644
index 5da44c7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/src/android/displaysize/app/SmallestWidthActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.displaysize.app;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class SmallestWidthActivity extends Activity {
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
-
- final Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean("launch_another_activity")) {
- Intent startIntent = new Intent();
- startIntent.setComponent(
- new ComponentName("android.server.cts", "android.server.cts.TestActivity"));
- startActivity(startIntent);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/AndroidManifest.xml
deleted file mode 100644
index 53aa2d4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.server.cts.second">
-
- <application>
- <activity
- android:name=".SecondActivity"
- android:resizeableActivity="true"
- android:allowEmbedded="true"
- android:exported="true" />
- <activity
- android:name=".SecondActivityNoEmbedding"
- android:resizeableActivity="true"
- android:allowEmbedded="false"
- android:exported="true" />
- <receiver
- android:name=".LaunchBroadcastReceiver"
- android:enabled="true"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.server.cts.second.LAUNCH_BROADCAST_ACTION"/>
- </intent-filter>
- </receiver>
- </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/LaunchBroadcastReceiver.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/LaunchBroadcastReceiver.java
deleted file mode 100644
index a8ebb86..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/LaunchBroadcastReceiver.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.second;
-
-import android.app.ActivityOptions;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-/** Broadcast receiver that can launch activities. */
-public class LaunchBroadcastReceiver extends BroadcastReceiver {
- private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final Bundle extras = intent.getExtras();
- final Intent newIntent = new Intent();
-
- final String targetActivity = extras != null ? extras.getString("target_activity") : null;
- if (targetActivity != null) {
- String packageName = extras.getString("package_name");
- newIntent.setComponent(new ComponentName(packageName,
- packageName + "." + targetActivity));
- } else {
- newIntent.setClass(context, SecondActivity.class);
- }
-
- ActivityOptions options = ActivityOptions.makeBasic();
- int displayId = extras.getInt("display_id", -1);
- if (displayId != -1) {
- options.setLaunchDisplayId(displayId);
- newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- } else {
- newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
-
- try {
- context.startActivity(newIntent, options.toBundle());
- } catch (SecurityException e) {
- Log.e(TAG, "SecurityException launching activity");
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivity.java
deleted file mode 100644
index cea9ed5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.second;
-
-import android.app.Activity;
-
-public class SecondActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivityNoEmbedding.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivityNoEmbedding.java
deleted file mode 100644
index 543e008..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/src/android/server/cts/second/SecondActivityNoEmbedding.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.second;
-
-import android.app.Activity;
-
-public class SecondActivityNoEmbedding extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/AndroidManifest.xml
deleted file mode 100644
index aed24c7..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.server.cts.third">
-
- <application>
- <activity android:name=".ThirdActivity"
- android:resizeableActivity="true"
- android:allowEmbedded="true"
- android:exported="true" />
- </application>
-
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/src/android/server/cts/third/ThirdActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/src/android/server/cts/third/ThirdActivity.java
deleted file mode 100644
index eeddc3d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/src/android/server/cts/third/ThirdActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts.third;
-
-import android.app.Activity;
-
-public class ThirdActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
deleted file mode 100644
index add7e42..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityAndWindowManagerOverrideConfigTests.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.server.cts.StateLogger.log;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityAndWindowManagerOverrideConfigTests
- */
-public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
- private static final String TEST_ACTIVITY_NAME = "LogConfigurationActivity";
-
- private class ConfigurationChangeObserver {
- private final Pattern mConfigurationChangedPattern =
- Pattern.compile("(.+)Configuration changed: (\\d+),(\\d+)");
-
- private ConfigurationChangeObserver() {
- }
-
- private boolean findConfigurationChange(String activityName, String logSeparator)
- throws DeviceNotAvailableException, InterruptedException {
- int tries = 0;
- boolean observedChange = false;
- while (tries < 5 && !observedChange) {
- final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
- log("Looking at logcat");
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- log(line);
- Matcher matcher = mConfigurationChangedPattern.matcher(line);
- if (matcher.matches()) {
- observedChange = true;
- break;
- }
- }
- tries++;
- Thread.sleep(500);
- }
- return observedChange;
- }
- }
-
- public void testReceiveOverrideConfigFromRelayout() throws Exception {
- if (!supportsFreeform()) {
- CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support freeform. Skipping test.");
- return;
- }
-
- launchActivityInStack(TEST_ACTIVITY_NAME, FREEFORM_WORKSPACE_STACK_ID);
-
- setDeviceRotation(0);
- String logSeparator = clearLogcat();
- resizeActivityTask(TEST_ACTIVITY_NAME, 0, 0, 100, 100);
- ConfigurationChangeObserver c = new ConfigurationChangeObserver();
- final boolean reportedSizeAfterResize = c.findConfigurationChange(TEST_ACTIVITY_NAME,
- logSeparator);
- assertTrue("Expected to observe configuration change when resizing",
- reportedSizeAfterResize);
-
- logSeparator = clearLogcat();
- setDeviceRotation(2);
- final boolean reportedSizeAfterRotation = c.findConfigurationChange(TEST_ACTIVITY_NAME,
- logSeparator);
- assertFalse("Not expected to observe configuration change after flip rotation",
- reportedSizeAfterRotation);
- }
-}
-
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
deleted file mode 100644
index c51f24a..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerActivityVisibilityTests.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.StateLogger.logE;
-
-import android.platform.test.annotations.Presubmit;
-
-import java.lang.Exception;
-import java.lang.String;
-
-import static com.android.ddmlib.Log.LogLevel.INFO;
-
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerActivityVisibilityTests
- */
-public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
- private static final String TRANSLUCENT_ACTIVITY = "AlwaysFocusablePipActivity";
- private static final String PIP_ON_PIP_ACTIVITY = "LaunchPipOnPipActivity";
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
- private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
- private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
- private static final String MOVE_TASK_TO_BACK_ACTIVITY_NAME = "MoveTaskToBackActivity";
- private static final String SWIPE_REFRESH_ACTIVITY = "SwipeRefreshActivity";
-
- private static final String NOHISTORY_ACTIVITY = "NoHistoryActivity";
- private static final String TURN_SCREEN_ON_ATTR_ACTIVITY_NAME = "TurnScreenOnAttrActivity";
- private static final String TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME = "TurnScreenOnShowOnLockActivity";
- private static final String TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME = "TurnScreenOnAttrRemoveAttrActivity";
- private static final String TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME = "TurnScreenOnSingleTaskActivity";
- private static final String TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY =
- "TurnScreenOnWithRelayoutActivity";
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- tearDownLockCredentials();
- }
-
- public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
- if (!supportsPip()) {
- return;
- }
-
- executeShellCommand(getAmStartCmdOverHome(PIP_ON_PIP_ACTIVITY));
- mAmWmState.waitForValidState(mDevice, PIP_ON_PIP_ACTIVITY);
- // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
- // translucent activity.
- executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
-
- mAmWmState.computeState(mDevice, new String[] {PIP_ON_PIP_ACTIVITY, TRANSLUCENT_ACTIVITY});
- mAmWmState.assertFrontStack("Pinned stack must be the front stack.", PINNED_STACK_ID);
- mAmWmState.assertVisibility(PIP_ON_PIP_ACTIVITY, true);
- mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
- }
-
- /**
- * Asserts that the home activity is visible when a translucent activity is launched in the
- * fullscreen stack over the home activity.
- */
- public void testTranslucentActivityOnTopOfHome() throws Exception {
- if (noHomeScreen()) {
- return;
- }
-
- launchHomeActivity();
- launchActivity(TRANSLUCENT_ACTIVITY);
-
- mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
- mAmWmState.assertFrontStack(
- "Fullscreen stack must be the front stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
- mAmWmState.assertHomeActivityVisible(true);
- }
-
- /**
- * Assert that the home activity is visible if a task that was launched from home is pinned
- * and also assert the next task in the fullscreen stack isn't visible.
- */
- public void testHomeVisibleOnActivityTaskPinned() throws Exception {
- if (!supportsPip()) {
- return;
- }
-
- launchHomeActivity();
- launchActivity(TEST_ACTIVITY_NAME);
- launchHomeActivity();
- launchActivity(TRANSLUCENT_ACTIVITY);
- executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
-
- mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
-
- mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
- mAmWmState.assertHomeActivityVisible(true);
- }
-
- public void testTranslucentActivityOverDockedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
- return;
- }
-
- launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME});
- launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME});
- launchActivityInStack(TRANSLUCENT_ACTIVITY_NAME, DOCKED_STACK_ID);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME,
- TRANSLUCENT_ACTIVITY_NAME}, false /* compareTaskAndStackBounds */);
- mAmWmState.assertContainsStack("Must contain docked stack", DOCKED_STACK_ID);
- mAmWmState.assertContainsStack("Must contain fullscreen stack",
- FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
- mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY_NAME, true);
- }
-
- @Presubmit
- public void testTurnScreenOnActivity() throws Exception {
- sleepDevice();
- launchActivity(TURN_SCREEN_ON_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ACTIVITY_NAME });
- mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY_NAME, true);
- }
-
- public void testFinishActivityInNonFocusedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
- return;
- }
-
- // Launch two activities in docked stack.
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- getLaunchActivityBuilder().setTargetActivityName(BROADCAST_RECEIVER_ACTIVITY).execute();
- mAmWmState.computeState(mDevice, new String[] { BROADCAST_RECEIVER_ACTIVITY });
- mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
- // Launch something to fullscreen stack to make it focused.
- launchActivityInStack(TEST_ACTIVITY_NAME, 1);
- mAmWmState.computeState(mDevice, new String[] { TEST_ACTIVITY_NAME });
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
- // Finish activity in non-focused (docked) stack.
- executeShellCommand(FINISH_ACTIVITY_BROADCAST);
- mAmWmState.computeState(mDevice, new String[] { LAUNCHING_ACTIVITY });
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
- }
-
- public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
- performFinishActivityWithMoveTaskToBack("on_pause");
- }
-
- public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
- performFinishActivityWithMoveTaskToBack("on_stop");
- }
-
- private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
- // Make sure home activity is visible.
- launchHomeActivity();
- if (!noHomeScreen()) {
- mAmWmState.assertHomeActivityVisible(true /* visible */);
- }
-
- // Launch an activity that calls "moveTaskToBack" to finish itself.
- launchActivity(MOVE_TASK_TO_BACK_ACTIVITY_NAME, "finish_point", finishPoint);
- mAmWmState.waitForValidState(mDevice, MOVE_TASK_TO_BACK_ACTIVITY_NAME);
- mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, true);
-
- // Launch a different activity on top.
- launchActivity(BROADCAST_RECEIVER_ACTIVITY);
- mAmWmState.waitForActivityState(mDevice, BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
- mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, false);
- mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
-
- // Finish the top-most activity.
- executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
- // Home must be visible.
- if (!noHomeScreen()) {
- mAmWmState.waitForHomeActivityVisible(mDevice);
- mAmWmState.assertHomeActivityVisible(true /* visible */);
- }
- }
-
- /**
- * Asserts that launching between reorder to front activities exhibits the correct backstack
- * behavior.
- */
- public void testReorderToFrontBackstack() throws Exception {
- // Start with home on top
- launchHomeActivity();
- if (!noHomeScreen()) {
- mAmWmState.assertHomeActivityVisible(true /* visible */);
- }
-
- // Launch the launching activity to the foreground
- launchActivity(LAUNCHING_ACTIVITY);
-
- // Launch the alternate launching activity from launching activity with reorder to front.
- getLaunchActivityBuilder().setTargetActivityName(ALT_LAUNCHING_ACTIVITY)
- .setReorderToFront(true).execute();
-
- // Launch the launching activity from the alternate launching activity with reorder to
- // front.
- getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY)
- .setLaunchingActivityName(ALT_LAUNCHING_ACTIVITY).setReorderToFront(true)
- .execute();
-
- // Press back
- pressBackButton();
-
- mAmWmState.waitForValidState(mDevice, ALT_LAUNCHING_ACTIVITY);
-
- // Ensure the alternate launching activity is in focus
- mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
- ALT_LAUNCHING_ACTIVITY);
- }
-
- /**
- * Asserts that the activity focus and history is preserved moving between the activity and
- * home stack.
- */
- public void testReorderToFrontChangingStack() throws Exception {
- // Start with home on top
- launchHomeActivity();
- if (!noHomeScreen()) {
- mAmWmState.assertHomeActivityVisible(true /* visible */);
- }
-
- // Launch the launching activity to the foreground
- launchActivity(LAUNCHING_ACTIVITY);
-
- // Launch the alternate launching activity from launching activity with reorder to front.
- getLaunchActivityBuilder().setTargetActivityName(ALT_LAUNCHING_ACTIVITY)
- .setReorderToFront(true).execute();
-
- // Return home
- launchHomeActivity();
- if (!noHomeScreen()) {
- mAmWmState.assertHomeActivityVisible(true /* visible */);
- }
- // Launch the launching activity from the alternate launching activity with reorder to
- // front.
-
- // Bring launching activity back to the foreground
- launchActivity(LAUNCHING_ACTIVITY);
- mAmWmState.waitForValidState(mDevice, LAUNCHING_ACTIVITY);
-
- // Ensure the alternate launching activity is still in focus.
- mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
- ALT_LAUNCHING_ACTIVITY);
-
- pressBackButton();
-
- mAmWmState.waitForValidState(mDevice, LAUNCHING_ACTIVITY);
-
- // Ensure launching activity was brought forward.
- mAmWmState.assertFocusedActivity("Launching Activity must be focused",
- LAUNCHING_ACTIVITY);
- }
-
- /**
- * Asserts that a nohistory activity is stopped and removed immediately after a resumed activity
- * above becomes visible and does not idle.
- */
- public void testNoHistoryActivityFinishedResumedActivityNotIdle() throws Exception {
- if (noHomeScreen()) {
- return;
- }
-
- // Start with home on top
- launchHomeActivity();
-
- // Launch no history activity
- launchActivity(NOHISTORY_ACTIVITY);
-
- // Launch an activity with a swipe refresh layout configured to prevent idle.
- launchActivity(SWIPE_REFRESH_ACTIVITY);
-
- pressBackButton();
- mAmWmState.waitForHomeActivityVisible(mDevice);
- mAmWmState.assertHomeActivityVisible(true);
- }
-
- public void testTurnScreenOnAttrNoLockScreen() throws Exception {
- wakeUpAndRemoveLock();
- sleepDevice();
- final String logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ATTR_ACTIVITY_NAME });
- mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, true);
- assertTrue(isDisplayOn());
- assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
- }
-
- public void testTurnScreenOnAttrWithLockScreen() throws Exception {
- if (!isHandheld()) {
- // This test requires the ability to have a lock screen.
- return;
- }
-
- setLockCredential();
- sleepDevice();
- final String logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ATTR_ACTIVITY_NAME });
- assertFalse(isDisplayOn());
- assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
- }
-
- public void testTurnScreenOnShowOnLockAttr() throws Exception {
- sleepDevice();
- mAmWmState.waitForAllStoppedActivities(mDevice);
- final String logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME });
- mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME, true);
- assertTrue(isDisplayOn());
- assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME, logSeparator);
- }
-
- public void testTurnScreenOnAttrRemove() throws Exception {
- sleepDevice();
- mAmWmState.waitForAllStoppedActivities(mDevice);
- String logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {
- TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME});
- assertTrue(isDisplayOn());
- assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
-
- sleepDevice();
- mAmWmState.waitForAllStoppedActivities(mDevice);
- logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
- assertFalse(isDisplayOn());
- assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
- }
-
- public void testTurnScreenOnSingleTask() throws Exception {
- sleepDevice();
- String logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME });
- mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, true);
- assertTrue(isDisplayOn());
- assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, logSeparator);
-
- sleepDevice();
- logSeparator = clearLogcat();
- launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME });
- mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, true);
- assertTrue(isDisplayOn());
- assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, logSeparator);
- }
-
- public void testTurnScreenOnActivity_withRelayout() throws Exception {
- sleepDevice();
- launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY });
- mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
-
- String logSeparator = clearLogcat();
- sleepDevice();
- mAmWmState.waitFor("Waiting for stopped state", () ->
- lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
-
- // Ensure there was an actual stop if the waitFor timed out.
- assertTrue(lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
- assertFalse(isDisplayOn());
- }
-
- private boolean lifecycleStopOccurred(String activityName, String logSeparator) {
- try {
- ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
- logSeparator);
- return lifecycleCounts.mStopCount > 0;
- } catch (DeviceNotAvailableException e) {
- logE(e.toString());
- return false;
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java
deleted file mode 100644
index f462c60..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmProfileTests.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.google.common.io.Files;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.util.FileUtil;
-
-import java.io.File;
-import java.lang.StringBuilder;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmProfileTests
- *
- * Please talk to Android Studio team first if you want to modify or delete these tests.
- */
-public class ActivityManagerAmProfileTests extends ActivityManagerTestBase {
-
- private static final String TEST_PACKAGE_NAME = "android.server.cts.debuggable";
- private static final String TEST_ACTIVITY_NAME = "DebuggableAppActivity";
- private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
- private static final String FIRST_WORD_NO_STREAMING = "*version\n";
- private static final String FIRST_WORD_STREAMING = "SLOW"; // Magic word set by runtime.
-
- private ITestDevice mDevice;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mDevice = getDevice();
- setComponentName(TEST_PACKAGE_NAME);
- }
-
- /**
- * Test am profile functionality with the following 3 configurable options:
- * starting the activity before start profiling? yes;
- * sampling-based profiling? no;
- * using streaming output mode? no.
- */
- public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
- // am profile start ... , and the same to the following 3 test methods.
- testProfile(true, false, false);
- }
-
- /**
- * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
- * only different in the three configuration options.
- */
- public void testAmProfileStartNoSamplingStreaming() throws Exception {
- testProfile(true, false, true);
- }
- public void testAmProfileStartSamplingNoStreaming() throws Exception {
- testProfile(true, true, false);
- }
- public void testAmProfileStartSamplingStreaming() throws Exception {
- testProfile(true, true, true);
- }
- public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
- // am start --start-profiler ..., and the same to the following 3 test methods.
- testProfile(false, false, false);
- }
- public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
- testProfile(false, false, true);
- }
- public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
- testProfile(false, true, false);
- }
- public void testAmStartStartProfilerSamplingStreaming() throws Exception {
- testProfile(false, true, true);
- }
-
- private void testProfile(boolean startActivityFirst,
- boolean sampling, boolean streaming) throws Exception {
- if (startActivityFirst) {
- launchActivity(TEST_ACTIVITY_NAME);
- }
-
- String cmd = getStartCmd(TEST_ACTIVITY_NAME, startActivityFirst, sampling, streaming);
- executeShellCommand(cmd);
- // Go to home screen and then warm start the activity to generate some interesting trace.
- pressHomeButton();
- launchActivity(TEST_ACTIVITY_NAME);
-
- cmd = "am profile stop " + componentName;
- executeShellCommand(cmd);
- // Sleep for 0.1 second (100 milliseconds) so the generation of the profiling
- // file is complete.
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- //ignored
- }
- verifyOutputFileFormat(streaming);
- }
-
- private String getStartCmd(String activityName, boolean activityAlreadyStarted,
- boolean sampling, boolean streaming) {
- StringBuilder builder = new StringBuilder("am");
- if (activityAlreadyStarted) {
- builder.append(" profile start");
- } else {
- builder.append(String.format(" start -n %s/.%s -W -S --start-profiler %s",
- componentName, activityName, OUTPUT_FILE_PATH));
- }
- if (sampling) {
- builder.append(" --sampling 1000");
- }
- if (streaming) {
- builder.append(" --streaming");
- }
- if (activityAlreadyStarted) {
- builder.append(String.format(" %s %s", componentName, OUTPUT_FILE_PATH));
- } else {
-
- }
- return builder.toString();
- }
-
- private void verifyOutputFileFormat(boolean streaming) throws Exception {
- String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
- byte[] data = readFileOnClient(OUTPUT_FILE_PATH);
- assertTrue("data size=" + data.length, data.length >= expectedFirstWord.length());
- String actualFirstWord = new String(data, 0, expectedFirstWord.length());
- assertTrue("Unexpected first word: '" + actualFirstWord + "'",
- actualFirstWord.equals(expectedFirstWord));
- // Clean up.
- executeShellCommand("rm -f " + OUTPUT_FILE_PATH);
- }
-
- private byte[] readFileOnClient(String clientPath) throws Exception {
- assertTrue("File not found on client: " + clientPath,
- mDevice.doesFileExist(clientPath));
- File copyOnHost = File.createTempFile("host", "copy");
- try {
- executeAdbCommand("pull", clientPath, copyOnHost.getPath());
- return Files.toByteArray(copyOnHost);
- } finally {
- FileUtil.deleteFile(copyOnHost);
- }
- }
-
- private String[] executeAdbCommand(String... command) throws DeviceNotAvailableException {
- String output = mDevice.executeAdbCommand(command);
- // "".split() returns { "" }, but we want an empty array
- String[] lines = output.equals("") ? new String[0] : output.split("\n");
- return lines;
- }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
deleted file mode 100644
index f6df4a8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAmStartOptionsTests
- */
-public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
-
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String ENTRYPOINT_ACTIVITY_NAME = "EntryPointAliasActivity";
- private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
-
- public void testDashD() throws Exception {
- final String activityComponentName =
- ActivityManagerTestBase.getActivityComponentName(TEST_ACTIVITY_NAME);
-
- final String[] waitForActivityRecords = new String[] {activityComponentName};
-
- // Run at least 2 rounds to verify that -D works with an existing process.
- // -D could fail in this case if the force stop of process is broken.
- int prevProcId = -1;
- for (int i = 0; i < 2; i++) {
- executeShellCommand(getAmStartCmd(TEST_ACTIVITY_NAME) + " -D");
-
- mAmWmState.waitForDebuggerWindowVisible(mDevice, waitForActivityRecords);
- int procId = mAmWmState.getAmState().getActivityProcId(activityComponentName);
-
- assertTrue("Invalid ProcId.", procId >= 0);
- if (i > 0) {
- assertTrue("Run " + i + " didn't start new proc.", prevProcId != procId);
- }
- prevProcId = procId;
- }
- }
-
- public void testDashW_Direct() throws Exception {
- testDashW(SINGLE_TASK_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
- }
-
- public void testDashW_Indirect() throws Exception {
- testDashW(ENTRYPOINT_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
- }
-
- private void testDashW(final String entryActivity, final String actualActivity)
- throws Exception {
- // Test cold start
- startActivityAndVerifyResult(entryActivity, actualActivity, true);
-
- // Test warm start
- pressHomeButton();
- startActivityAndVerifyResult(entryActivity, actualActivity, false);
-
- // Test "hot" start (app already in front)
- startActivityAndVerifyResult(entryActivity, actualActivity, false);
- }
-
- private void startActivityAndVerifyResult(final String entryActivity,
- final String actualActivity, boolean shouldStart) throws Exception {
- // See TODO below
- // final String logSeparator = clearLogcat();
-
- // Pass in different data only when cold starting. This is to make the intent
- // different in subsequent warm/hot launches, so that the entrypoint alias
- // activity is always started, but the actual activity is not started again
- // because of the NEW_TASK and singleTask flags.
- final String result = executeShellCommand(getAmStartCmd(entryActivity) + " -W"
- + (shouldStart ? " -d about:blank" : ""));
-
- // Verify shell command return value
- verifyShellOutput(result, actualActivity, shouldStart);
-
- // TODO: Disable logcat check for now.
- // Logcat of WM or AM tag could be lost (eg. chatty if earlier events generated
- // too many lines), and make the test look flaky. We need to either use event
- // log or swith to other mechanisms. Only verify shell output for now, it should
- // still catch most failures.
-
- // Verify adb logcat log
- //verifyLogcat(actualActivity, shouldStart, logSeparator);
- }
-
- private static final Pattern sNotStartedWarningPattern = Pattern.compile(
- "Warning: Activity not started(.*)");
- private static final Pattern sStatusPattern = Pattern.compile(
- "Status: (.*)");
- private static final Pattern sActivityPattern = Pattern.compile(
- "Activity: (.*)");
- private static final String sStatusOk = "ok";
-
- private void verifyShellOutput(
- final String result, final String activity, boolean shouldStart) {
- boolean warningFound = false;
- String status = null;
- String reportedActivity = null;
- String componentActivityName = getActivityComponentName(activity);
-
- final String[] lines = result.split("\\n");
- // Going from the end of logs to beginning in case if some other activity is started first.
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- Matcher matcher = sNotStartedWarningPattern.matcher(line);
- if (matcher.matches()) {
- warningFound = true;
- continue;
- }
- matcher = sStatusPattern.matcher(line);
- if (matcher.matches()) {
- status = matcher.group(1);
- continue;
- }
- matcher = sActivityPattern.matcher(line);
- if (matcher.matches()) {
- reportedActivity = matcher.group(1);
- continue;
- }
- }
-
- assertTrue("Status " + status + " is not ok", sStatusOk.equals(status));
- assertTrue("Reported activity is " + reportedActivity + " not " + componentActivityName,
- componentActivityName.equals(reportedActivity));
-
- if (shouldStart && warningFound) {
- fail("Should start new activity but brought something to front.");
- } else if (!shouldStart && !warningFound){
- fail("Should bring existing activity to front but started new activity.");
- }
- }
-
- private static final Pattern sDisplayTimePattern =
- Pattern.compile("(.+): Displayed (.*): (\\+{0,1})([0-9]+)ms(.*)");
-
- void verifyLogcat(String actualActivityName, boolean shouldStart, String logSeparator)
- throws DeviceNotAvailableException {
- int displayCount = 0;
- String activityName = null;
-
- for (String line : getDeviceLogsForComponent("ActivityManager", logSeparator)) {
- line = line.trim();
-
- Matcher matcher = sDisplayTimePattern.matcher(line);
- if (matcher.matches()) {
- activityName = matcher.group(2);
- // Ignore activitiy displays from other packages, we don't
- // want some random activity starts to ruin our test.
- if (!activityName.startsWith("android.server.cts")) {
- continue;
- }
- if (!shouldStart) {
- fail("Shouldn't display anything but displayed " + activityName);
- }
- displayCount++;
- }
- }
- final String expectedActivityName = getActivityComponentName(actualActivityName);
- if (shouldStart) {
- if (displayCount != 1) {
- fail("Should display exactly one activity but displayed " + displayCount);
- } else if (!expectedActivityName.equals(activityName)) {
- fail("Should display " + expectedActivityName +
- " but displayed " + activityName);
- }
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
deleted file mode 100644
index ccecd1d..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ /dev/null
@@ -1,588 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-
-import android.platform.test.annotations.Presubmit;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.List;
-
-import static android.server.cts.ActivityAndWindowManagersState.dpToPx;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerAppConfigurationTests
- */
-public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
- private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String PORTRAIT_ACTIVITY_NAME = "PortraitOrientationActivity";
- private static final String LANDSCAPE_ACTIVITY_NAME = "LandscapeOrientationActivity";
- private static final String NIGHT_MODE_ACTIVITY = "NightModeActivity";
- private static final String DIALOG_WHEN_LARGE_ACTIVITY = "DialogWhenLargeActivity";
-
- private static final String TRANSLUCENT_ACTIVITY =
- "android.server.translucentapp.TranslucentLandscapeActivity";
- private static final String TRANSLUCENT_SDK_26_PACKAGE = "android.server.translucentapp26";
- private static final String TRANSLUCENT_CURRENT_PACKAGE = "android.server.translucentapp";
-
- private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
-
- private static final int SMALL_WIDTH_DP = 426;
- private static final int SMALL_HEIGHT_DP = 320;
-
- /**
- * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
- * has an updated size when the Activity is resized from fullscreen to docked state.
- *
- * The Activity handles configuration changes, so it will not be restarted between resizes.
- * On Configuration changes, the Activity logs the Display size and Configuration width
- * and heights. The values reported in fullscreen should be larger than those reported in
- * docked state.
- */
- public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- String logSeparator = clearLogcat();
- launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- logSeparator = clearLogcat();
- moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
- final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- assertSizesAreSane(fullscreenSizes, dockedSizes);
- }
-
- /**
- * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
- * from docked state to fullscreen (reverse).
- */
- // TODO: Flaky, add to presubmit when b/63404575 is fixed.
- public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- String logSeparator = clearLogcat();
- launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
- final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- logSeparator = clearLogcat();
- moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- assertSizesAreSane(fullscreenSizes, dockedSizes);
- }
-
- /**
- * Tests whether the Display sizes change when rotating the device.
- */
- public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
- if (!supportsRotation()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no rotation support");
- return;
- }
- setDeviceRotation(0);
- final String logSeparator = clearLogcat();
- launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- rotateAndCheckSizes(initialSizes);
- }
-
- /**
- * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
- * is in the docked stack.
- */
- // TODO: Flaky, add to presubmit when b/63404575 is fixed.
- public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- setDeviceRotation(0);
- final String logSeparator = clearLogcat();
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- // Launch our own activity to side in case Recents (or other activity to side) doesn't
- // support rotation.
- getLaunchActivityBuilder().setToSide(true).setTargetActivityName(TEST_ACTIVITY_NAME)
- .execute();
- // Launch target activity in docked stack.
- getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
- final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- rotateAndCheckSizes(initialSizes);
- }
-
- /**
- * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
- * is launched to side from docked stack.
- */
- public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- setDeviceRotation(0);
-
- final String logSeparator = clearLogcat();
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
-
- getLaunchActivityBuilder().setToSide(true).setTargetActivityName(RESIZEABLE_ACTIVITY_NAME)
- .execute();
- final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
-
- rotateAndCheckSizes(initialSizes);
- }
-
- private void rotateAndCheckSizes(ReportedSizes prevSizes) throws Exception {
- for (int rotation = 3; rotation >= 0; --rotation) {
- final String logSeparator = clearLogcat();
- final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
- RESIZEABLE_ACTIVITY_NAME).mStackId;
- final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
- setDeviceRotation(rotation);
- final int newDeviceRotation = getDeviceRotation(displayId);
- if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
- CLog.logAndDisplay(LogLevel.WARN, "Got an invalid device rotation value. "
- + "Continuing the test despite of that, but it is likely to fail.");
- } else if (rotation != newDeviceRotation) {
- CLog.logAndDisplay(LogLevel.INFO, "This device doesn't support locked user "
- + "rotation mode. Not continuing the rotation checks.");
- return;
- }
-
- final ReportedSizes rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
- assertSizesRotate(prevSizes, rotatedSizes);
- prevSizes = rotatedSizes;
- }
- }
-
- /**
- * Tests when activity moved from fullscreen stack to docked and back. Activity will be
- * relaunched twice and it should have same config as initial one.
- */
- public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
- moveActivityFullSplitFull(TEST_ACTIVITY_NAME);
- }
-
- /**
- * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
- */
- @Presubmit
- public void testSameConfigurationFullSplitFullNoRelaunch() throws Exception {
- moveActivityFullSplitFull(RESIZEABLE_ACTIVITY_NAME);
- }
-
- /**
- * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
- * Last operation is done in a way which simulates split-screen divider movement maximizing
- * docked stack size and then moving task to fullscreen stack - the same way it is done when
- * user long-presses overview/recents button to exit split-screen.
- * Asserts that initial and final reported sizes in fullscreen stack are the same.
- */
- private void moveActivityFullSplitFull(String activityName) throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- // Launch to fullscreen stack and record size.
- String logSeparator = clearLogcat();
- launchActivityInStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
- logSeparator);
- final Rectangle displayRect = getDisplayRect(activityName);
-
- // Move to docked stack.
- logSeparator = clearLogcat();
- moveActivityToStack(activityName, DOCKED_STACK_ID);
- final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, logSeparator);
- assertSizesAreSane(initialFullscreenSizes, dockedSizes);
- // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
- // will come up.
- launchActivityInStack(activityName, DOCKED_STACK_ID);
- mAmWmState.computeState(mDevice, new String[] { activityName },
- false /* compareTaskAndStackBounds */);
-
- // Resize docked stack to fullscreen size. This will trigger activity relaunch with
- // non-empty override configuration corresponding to fullscreen size.
- logSeparator = clearLogcat();
- runCommandAndPrintOutput("am stack resize " + DOCKED_STACK_ID + " 0 0 "
- + displayRect.width + " " + displayRect.height);
- // Move activity back to fullscreen stack.
- moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
- logSeparator);
-
- // After activity configuration was changed twice it must report same size as original one.
- assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
- }
-
- /**
- * Tests when activity moved from docked stack to fullscreen and back. Activity will be
- * relaunched twice and it should have same config as initial one.
- */
- public void testSameConfigurationSplitFullSplitRelaunch() throws Exception {
- moveActivitySplitFullSplit(TEST_ACTIVITY_NAME);
- }
-
- /**
- * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
- */
- public void testSameConfigurationSplitFullSplitNoRelaunch() throws Exception {
- moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY_NAME);
- }
-
- /**
- * Tests that an activity with the DialogWhenLarge theme can transform properly when in split
- * screen.
- */
- @Presubmit
- public void testDialogWhenLargeSplitSmall() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- launchActivityInStack(DIALOG_WHEN_LARGE_ACTIVITY, DOCKED_STACK_ID);
- final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
- .getStackById(DOCKED_STACK_ID);
- final WindowManagerState.Display display =
- mAmWmState.getWmState().getDisplay(stack.mDisplayId);
- final int density = display.getDpi();
- final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
- final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
-
- runCommandAndPrintOutput("am stack resize " + DOCKED_STACK_ID + " 0 0 "
- + smallWidthPx + " " + smallHeightPx);
- mAmWmState.waitForValidState(mDevice, DIALOG_WHEN_LARGE_ACTIVITY, DOCKED_STACK_ID);
- }
-
- /**
- * Test that device handles consequent requested orientations and displays the activities.
- */
- @Presubmit
- public void testFullscreenAppOrientationRequests() throws Exception {
- launchActivity(PORTRAIT_ACTIVITY_NAME);
- mAmWmState.assertVisibility(PORTRAIT_ACTIVITY_NAME, true /* visible */);
- assertEquals("Fullscreen app requested portrait orientation",
- 1 /* portrait */, mAmWmState.getWmState().getLastOrientation());
-
- launchActivity(LANDSCAPE_ACTIVITY_NAME);
- mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
- assertEquals("Fullscreen app requested landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
- launchActivity(PORTRAIT_ACTIVITY_NAME);
- mAmWmState.assertVisibility(PORTRAIT_ACTIVITY_NAME, true /* visible */);
- assertEquals("Fullscreen app requested portrait orientation",
- 1 /* portrait */, mAmWmState.getWmState().getLastOrientation());
- }
-
- public void testNonfullscreenAppOrientationRequests() throws Exception {
- String logSeparator = clearLogcat();
- launchActivity(PORTRAIT_ACTIVITY_NAME);
- final ReportedSizes initialReportedSizes =
- getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
- assertEquals("portrait activity should be in portrait",
- 1 /* portrait */, initialReportedSizes.orientation);
- logSeparator = clearLogcat();
-
- launchActivityInComponent(TRANSLUCENT_SDK_26_PACKAGE, TRANSLUCENT_ACTIVITY);
-
- assertEquals("Legacy non-fullscreen activity requested landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
- // TODO(b/36897968): uncomment once we can suppress unsupported configurations
- // final ReportedSizes updatedReportedSizes =
- // getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
- // assertEquals("portrait activity should not have moved from portrait",
- // 1 /* portrait */, updatedReportedSizes.orientation);
- }
-
- public void testNonFullscreenActivityPermitted() throws Exception {
- setComponentName(TRANSLUCENT_CURRENT_PACKAGE);
- setDeviceRotation(0);
-
- launchActivity(TRANSLUCENT_ACTIVITY);
- mAmWmState.assertResumedActivity(
- "target SDK non-fullscreen activity should be allowed to launch",
- TRANSLUCENT_ACTIVITY);
- assertEquals("non-fullscreen activity requested landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
- }
-
- /**
- * Test that device handles moving between two tasks with different orientations.
- */
- public void testTaskCloseRestoreOrientation() throws Exception {
- // Start landscape activity.
- launchActivity(LANDSCAPE_ACTIVITY_NAME);
- mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
- assertEquals("Fullscreen app requested landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
- // Start another activity in a different task.
- launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
-
- // Request portrait
- executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
- mAmWmState.waitForRotation(mDevice, 1);
-
- // Finish activity
- executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
- // Verify that activity brought to front is in originally requested orientation.
- mAmWmState.computeState(mDevice, new String[]{LANDSCAPE_ACTIVITY_NAME});
- assertEquals("Should return to app in landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
- }
-
- /**
- * Test that device handles moving between two tasks with different orientations.
- */
- public void testTaskMoveToBackOrientation() throws Exception {
- // Start landscape activity.
- launchActivity(LANDSCAPE_ACTIVITY_NAME);
- mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
- assertEquals("Fullscreen app requested landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
-
- // Start another activity in a different task.
- launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
-
- // Request portrait
- executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
- mAmWmState.waitForRotation(mDevice, 1);
-
- // Finish activity
- executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
-
- // Verify that activity brought to front is in originally requested orientation.
- mAmWmState.waitForValidState(mDevice, LANDSCAPE_ACTIVITY_NAME);
- assertEquals("Should return to app in landscape orientation",
- 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
- }
-
- /**
- * Test that device doesn't change device orientation by app request while in multi-window.
- */
- public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
- requestOrientationInSplitScreen(1 /* portrait */, LANDSCAPE_ACTIVITY_NAME);
- }
-
- /**
- * Test that device doesn't change device orientation by app request while in multi-window.
- */
- public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
- requestOrientationInSplitScreen(0 /* landscape */, PORTRAIT_ACTIVITY_NAME);
- }
-
- /**
- * Rotate the device and launch specified activity in split-screen, checking if orientation
- * didn't change.
- */
- private void requestOrientationInSplitScreen(int orientation, String activity)
- throws Exception {
- // Set initial orientation.
- setDeviceRotation(orientation);
-
- // Launch activities that request orientations and check that device doesn't rotate.
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
-
- getLaunchActivityBuilder().setToSide(true).setMultipleTask(true)
- .setTargetActivityName(activity).execute();
- mAmWmState.computeState(mDevice, new String[] {activity});
- mAmWmState.assertVisibility(activity, true /* visible */);
- assertEquals("Split-screen apps shouldn't influence device orientation",
- orientation, mAmWmState.getWmState().getRotation());
-
- getLaunchActivityBuilder().setMultipleTask(true).setTargetActivityName(activity).execute();
- mAmWmState.computeState(mDevice, new String[] {activity});
- mAmWmState.assertVisibility(activity, true /* visible */);
- assertEquals("Split-screen apps shouldn't influence device orientation",
- orientation, mAmWmState.getWmState().getRotation());
- }
-
- /**
- * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
- * Asserts that initial and final reported sizes in docked stack are the same.
- */
- private void moveActivitySplitFullSplit(String activityName) throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- // Launch to docked stack and record size.
- String logSeparator = clearLogcat();
- launchActivityInStack(activityName, DOCKED_STACK_ID);
- final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName, logSeparator);
- // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
- // will come up.
- launchActivityInStack(activityName, DOCKED_STACK_ID);
- mAmWmState.computeState(mDevice, new String[] { activityName },
- false /* compareTaskAndStackBounds */);
-
- // Move to fullscreen stack.
- logSeparator = clearLogcat();
- moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
- final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName, logSeparator);
- assertSizesAreSane(fullscreenSizes, initialDockedSizes);
-
- // Move activity back to docked stack.
- logSeparator = clearLogcat();
- moveActivityToStack(activityName, DOCKED_STACK_ID);
- final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName, logSeparator);
-
- // After activity configuration was changed twice it must report same size as original one.
- assertSizesAreSame(initialDockedSizes, finalDockedSizes);
- }
-
- /**
- * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
- * have flipped.
- */
- private static void assertSizesRotate(ReportedSizes rotationA, ReportedSizes rotationB)
- throws Exception {
- assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
- assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
- assertEquals(rotationB.displayWidth, rotationB.metricsWidth);
- assertEquals(rotationB.displayHeight, rotationB.metricsHeight);
-
- final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight;
- final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight;
- assertFalse(beforePortrait == afterPortrait);
-
- final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp;
- final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp;
- assertEquals(beforePortrait, beforeConfigPortrait);
- assertEquals(afterPortrait, afterConfigPortrait);
-
- assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp);
- }
-
- /**
- * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
- * that are smaller than the dockedSizes.
- */
- private static void assertSizesAreSane(ReportedSizes fullscreenSizes, ReportedSizes dockedSizes)
- throws Exception {
- final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
- if (portrait) {
- assertTrue(dockedSizes.displayHeight < fullscreenSizes.displayHeight);
- assertTrue(dockedSizes.heightDp < fullscreenSizes.heightDp);
- assertTrue(dockedSizes.metricsHeight < fullscreenSizes.metricsHeight);
- } else {
- assertTrue(dockedSizes.displayWidth < fullscreenSizes.displayWidth);
- assertTrue(dockedSizes.widthDp < fullscreenSizes.widthDp);
- assertTrue(dockedSizes.metricsWidth < fullscreenSizes.metricsWidth);
- }
- }
-
- /**
- * Throws an AssertionError if sizes are different.
- */
- private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize)
- throws Exception {
- assertEquals(firstSize.widthDp, secondSize.widthDp);
- assertEquals(firstSize.heightDp, secondSize.heightDp);
- assertEquals(firstSize.displayWidth, secondSize.displayWidth);
- assertEquals(firstSize.displayHeight, secondSize.displayHeight);
- assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
- assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
- assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
- }
-
- private ReportedSizes getActivityDisplaySize(String activityName, String logSeparator)
- throws Exception {
- mAmWmState.computeState(mDevice, new String[] { activityName },
- false /* compareTaskAndStackBounds */);
- final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
- assertNotNull(details);
- return details;
- }
-
- private Rectangle getDisplayRect(String activityName)
- throws Exception {
- final String windowName = getWindowName(activityName);
-
- mAmWmState.computeState(mDevice, new String[] {activityName});
- mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
-
- final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
- mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
-
- assertEquals("Should have exactly one window state for the activity.", 1,
- tempWindowList.size());
-
- WindowManagerState.WindowState windowState = tempWindowList.get(0);
- assertNotNull("Should have a valid window", windowState);
-
- WindowManagerState.Display display = mAmWmState.getWmState()
- .getDisplay(windowState.getDisplayId());
- assertNotNull("Should be on a display", display);
-
- return display.getDisplayRect();
- }
-
- /**
- * Test launching an activity which requests specific UI mode during creation.
- */
- public void testLaunchWithUiModeChange() throws Exception {
- // Launch activity that changes UI mode and handles this configuration change.
- launchActivity(NIGHT_MODE_ACTIVITY);
- mAmWmState.waitForActivityState(mDevice, NIGHT_MODE_ACTIVITY, STATE_RESUMED);
-
- // Check if activity is launched successfully.
- mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Launched activity should be focused",
- NIGHT_MODE_ACTIVITY);
- mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAssistantStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAssistantStackTests.java
deleted file mode 100644
index be2af68..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAssistantStackTests.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test android.server.cts.ActivityManagerAssistantStackTests
- */
-public class ActivityManagerAssistantStackTests extends ActivityManagerTestBase {
-
- private static final String VOICE_INTERACTION_SERVICE = "AssistantVoiceInteractionService";
-
- private static final String TEST_ACTIVITY = "TestActivity";
- private static final String ANIMATION_TEST_ACTIVITY = "AnimationTestActivity";
- private static final String DOCKED_ACTIVITY = "DockedActivity";
- private static final String ASSISTANT_ACTIVITY = "AssistantActivity";
- private static final String TRANSLUCENT_ASSISTANT_ACTIVITY = "TranslucentAssistantActivity";
- private static final String LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION =
- "LaunchAssistantActivityFromSession";
- private static final String LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK =
- "LaunchAssistantActivityIntoAssistantStack";
- private static final String PIP_ACTIVITY = "PipActivity";
-
- private static final String EXTRA_ENTER_PIP = "enter_pip";
- private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
- private static final String EXTRA_FINISH_SELF = "finish_self";
- public static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
-
- private static final String TEST_ACTIVITY_ACTION_FINISH_SELF =
- "android.server.cts.TestActivity.finish_self";
-
- public void testLaunchingAssistantActivityIntoAssistantStack() throws Exception {
- // Enable the assistant and launch an assistant activity
- enableAssistant();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
- mAmWmState.waitForValidState(mDevice, ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
-
- // Ensure that the activity launched in the fullscreen assistant stack
- assertAssistantStackExists();
- assertTrue("Expected assistant stack to be fullscreen",
- mAmWmState.getAmState().getStackById(ASSISTANT_STACK_ID).isFullscreen());
-
- disableAssistant();
- }
-
- public void testAssistantStackZOrder() throws Exception {
- if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
- // Launch a pinned stack task
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
-
- // Dock a task
- launchActivity(TEST_ACTIVITY);
- launchActivityInDockStack(DOCKED_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain fullscreen stack.",
- FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
- // Enable the assistant and launch an assistant activity, ensure it is on top
- enableAssistant();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
- mAmWmState.waitForValidState(mDevice, ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
- assertAssistantStackExists();
-
- mAmWmState.assertFrontStack("Pinned stack should be on top.", PINNED_STACK_ID);
- mAmWmState.assertFocusedStack("Assistant stack should be focused.", ASSISTANT_STACK_ID);
-
- disableAssistant();
- }
-
- public void testAssistantStackLaunchNewTask() throws Exception {
- enableAssistant();
- assertAssistantStackCanLaunchAndReturnFromNewTask();
- disableAssistant();
- }
-
- public void testAssistantStackLaunchNewTaskWithDockedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) return;
- // Dock a task
- launchActivity(TEST_ACTIVITY);
- launchActivityInDockStack(DOCKED_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain fullscreen stack.",
- FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
- enableAssistant();
- assertAssistantStackCanLaunchAndReturnFromNewTask();
- disableAssistant();
- }
-
- private void assertAssistantStackCanLaunchAndReturnFromNewTask() throws Exception {
- // Enable the assistant and launch an assistant activity which will launch a new task
- enableAssistant();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_LAUNCH_NEW_TASK, TEST_ACTIVITY);
- disableAssistant();
-
- // Ensure that the fullscreen stack is on top and the test activity is now visible
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
- mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
- FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
- FULLSCREEN_WORKSPACE_STACK_ID);
-
- // Now, tell it to finish itself and ensure that the assistant stack is brought back forward
- executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
- mAmWmState.waitForFocusedStack(mDevice, ASSISTANT_STACK_ID);
- mAmWmState.assertFrontStack("Assistant stack should be on top.", ASSISTANT_STACK_ID);
- mAmWmState.assertFocusedStack("Assistant stack should be focused.", ASSISTANT_STACK_ID);
- }
-
- public void testAssistantStackFinishToPreviousApp() throws Exception {
- // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
- // the fullscreen activity is still visible and on top after the assistant activity finishes
- launchActivity(TEST_ACTIVITY);
- enableAssistant();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_FINISH_SELF, "true");
- disableAssistant();
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY, STATE_RESUMED);
- mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
- mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
- FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
- FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- public void testDisallowEnterPiPFromAssistantStack() throws Exception {
- enableAssistant();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_ENTER_PIP, "true");
- disableAssistant();
- mAmWmState.waitForValidState(mDevice, ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
- mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.", PINNED_STACK_ID);
- }
-
- public void testTranslucentAssistantActivityStackVisibility() throws Exception {
- enableAssistant();
- // Go home, launch the assistant and check to see that home is visible
- removeStacks(FULLSCREEN_WORKSPACE_STACK_ID);
- launchHomeActivity();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_IS_TRANSLUCENT, String.valueOf(true));
- mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
- assertAssistantStackExists();
- if (!noHomeScreen()) {
- mAmWmState.assertHomeActivityVisible(true);
- }
-
- // Launch a fullscreen app and then launch the assistant and check to see that it is
- // also visible
- removeStacks(ASSISTANT_STACK_ID);
- launchActivity(TEST_ACTIVITY);
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_IS_TRANSLUCENT, String.valueOf(true));
- mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
- assertAssistantStackExists();
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
- // Go home, launch assistant, launch app into fullscreen with activity present, and go back.
- // Ensure home is visible.
- removeStacks(ASSISTANT_STACK_ID);
- launchHomeActivity();
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_IS_TRANSLUCENT, String.valueOf(true), EXTRA_LAUNCH_NEW_TASK,
- TEST_ACTIVITY);
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertHomeActivityVisible(false);
- pressBackButton();
- mAmWmState.waitForFocusedStack(mDevice, ASSISTANT_STACK_ID);
- assertAssistantStackExists();
- if (!noHomeScreen()) {
- mAmWmState.assertHomeActivityVisible(true);
- }
-
- // Launch a fullscreen and docked app and then launch the assistant and check to see that it
- // is also visible
- if (supportsSplitScreenMultiWindow()) {
- removeStacks(ASSISTANT_STACK_ID);
- launchActivityInDockStack(DOCKED_ACTIVITY);
- launchActivity(TEST_ACTIVITY);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_IS_TRANSLUCENT, String.valueOf(true));
- mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
- assertAssistantStackExists();
- mAmWmState.assertVisibility(DOCKED_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
- }
- disableAssistant();
- }
-
- public void testLaunchIntoSameTask() throws Exception {
- enableAssistant();
-
- // Launch the assistant
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
- assertAssistantStackExists();
- mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
- mAmWmState.assertFocusedStack("Expected assistant stack focused", ASSISTANT_STACK_ID);
- assertEquals(1, mAmWmState.getAmState().getStackById(ASSISTANT_STACK_ID).getTasks().size());
- final int taskId = mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY)
- .mTaskId;
-
- // Launch a new fullscreen activity
- // Using Animation Test Activity because it is opaque on all devices.
- launchActivity(ANIMATION_TEST_ACTIVITY);
- mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, false);
-
- // Launch the assistant again and ensure that it goes into the same task
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
- assertAssistantStackExists();
- mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
- mAmWmState.assertFocusedStack("Expected assistant stack focused", ASSISTANT_STACK_ID);
- assertEquals(1, mAmWmState.getAmState().getStackById(ASSISTANT_STACK_ID).getTasks().size());
- assertEquals(taskId,
- mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY).mTaskId);
-
- disableAssistant();
- }
-
- public void testPinnedStackWithAssistant() throws Exception {
- if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
-
- enableAssistant();
-
- // Launch a fullscreen activity and a PIP activity, then launch the assistant, and ensure
- // that the test activity is still visible
- launchActivity(TEST_ACTIVITY);
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
- EXTRA_IS_TRANSLUCENT, String.valueOf(true));
- mAmWmState.waitForValidState(mDevice, TRANSLUCENT_ASSISTANT_ACTIVITY, ASSISTANT_STACK_ID);
- assertAssistantStackExists();
- mAmWmState.assertVisibility(TRANSLUCENT_ASSISTANT_ACTIVITY, true);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
- disableAssistant();
- }
-
- /**
- * Asserts that the assistant stack exists.
- */
- private void assertAssistantStackExists() throws Exception {
- mAmWmState.assertContainsStack("Must contain assistant stack.", ASSISTANT_STACK_ID);
- }
-
- /**
- * Asserts that the assistant stack does not exist.
- */
- private void assertAssistantStackDoesNotExist() throws Exception {
- mAmWmState.assertDoesNotContainStack("Must not contain assistant stack.",
- ASSISTANT_STACK_ID);
- }
-
- /**
- * Sets the system voice interaction service.
- */
- private void enableAssistant() throws Exception {
- executeShellCommand("settings put secure voice_interaction_service " +
- getActivityComponentName(VOICE_INTERACTION_SERVICE));
- }
-
- /**
- * Resets the system voice interaction service.
- */
- private void disableAssistant() throws Exception {
- executeShellCommand("settings delete secure voice_interaction_service " +
- getActivityComponentName(VOICE_INTERACTION_SERVICE));
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
deleted file mode 100644
index 4640d19..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.StateLogger.logE;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerConfigChangeTests
- */
-public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
-
- private static final String FONT_SCALE_ACTIVITY_NAME = "FontScaleActivity";
- private static final String FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME =
- "FontScaleNoRelaunchActivity";
-
- private static final float EXPECTED_FONT_SIZE_SP = 10.0f;
-
- public void testRotation90Relaunch() throws Exception{
- // Should relaunch on every rotation and receive no onConfigurationChanged()
- testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
- }
-
- public void testRotation90NoRelaunch() throws Exception {
- // Should receive onConfigurationChanged() on every rotation and no relaunch
- testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
- }
-
- public void testRotation180Relaunch() throws Exception {
- // Should receive nothing
- testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
- }
-
- public void testRotation180NoRelaunch() throws Exception {
- // Should receive nothing
- testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
- }
-
- @Presubmit
- public void testChangeFontScaleRelaunch() throws Exception {
- // Should relaunch and receive no onConfigurationChanged()
- testChangeFontScale(FONT_SCALE_ACTIVITY_NAME, true /* relaunch */);
- }
-
- @Presubmit
- public void testChangeFontScaleNoRelaunch() throws Exception {
- // Should receive onConfigurationChanged() and no relaunch
- testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME, false /* relaunch */);
- }
-
- private void testRotation(
- String activityName, int rotationStep, int numRelaunch, int numConfigChange)
- throws Exception {
- launchActivity(activityName);
-
- final String[] waitForActivitiesVisible = new String[] {activityName};
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
- final int initialRotation = 4 - rotationStep;
- setDeviceRotation(initialRotation);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
- activityName).mStackId;
- final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
- final int newDeviceRotation = getDeviceRotation(displayId);
- if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
- CLog.logAndDisplay(LogLevel.WARN, "Got an invalid device rotation value. "
- + "Continuing the test despite of that, but it is likely to fail.");
- } else if (newDeviceRotation != initialRotation) {
- CLog.logAndDisplay(LogLevel.INFO, "This device doesn't support user rotation "
- + "mode. Not continuing the rotation checks.");
- return;
- }
-
- for (int rotation = 0; rotation < 4; rotation += rotationStep) {
- final String logSeparator = clearLogcat();
- setDeviceRotation(rotation);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange, logSeparator);
- }
- }
-
- private void testChangeFontScale(
- String activityName, boolean relaunch) throws Exception {
- launchActivity(activityName);
- final String[] waitForActivitiesVisible = new String[] {activityName};
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
- setFontScale(1.0f);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-
- final int densityDpi = getGlobalDensityDpi();
-
- for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
- final String logSeparator = clearLogcat();
- setFontScale(fontScale);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1,
- logSeparator);
-
- // Verify that the display metrics are updated, and therefore the text size is also
- // updated accordingly.
- assertExpectedFontPixelSize(activityName,
- scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi),
- logSeparator);
- }
- }
-
- /**
- * Test updating application info when app is running. An activity with matching package name
- * must be recreated and its asset sequence number must be incremented.
- */
- public void testUpdateApplicationInfo() throws Exception {
- final String firstLogSeparator = clearLogcat();
-
- // Launch an activity that prints applied config.
- launchActivity(TEST_ACTIVITY_NAME);
- final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, firstLogSeparator);
-
- final String logSeparator = clearLogcat();
- // Update package info.
- executeShellCommand("am update-appinfo all " + componentName);
- mAmWmState.waitForWithAmState(mDevice, (amState) -> {
- // Wait for activity to be resumed and asset seq number to be updated.
- try {
- return readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator) == assetSeq + 1
- && amState.hasActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
- } catch (Exception e) {
- logE("Error waiting for valid state: " + e.getMessage());
- return false;
- }
- }, "Waiting asset sequence number to be updated and for activity to be resumed.");
-
- // Check if activity is relaunched and asset seq is updated.
- assertRelaunchOrConfigChanged(TEST_ACTIVITY_NAME, 1 /* numRelaunch */,
- 0 /* numConfigChange */, logSeparator);
- final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator);
- assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
- }
-
- private static final Pattern sConfigurationPattern = Pattern.compile(
- "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
-
- /** Read asset sequence number in last applied configuration from logs. */
- private int readAssetSeqNumber(String activityName, String logSeparator) throws Exception {
- final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- final Matcher matcher = sConfigurationPattern.matcher(line);
- if (matcher.matches()) {
- final String assetSeqNumber = matcher.group(3);
- try {
- return Integer.valueOf(assetSeqNumber);
- } catch (NumberFormatException e) {
- // Ignore, asset seq number is not printed when not set.
- }
- }
- }
- return 0;
- }
-
- // Calculate the scaled pixel size just like the device is supposed to.
- private static int scaledPixelsToPixels(float sp, float fontScale, int densityDpi) {
- final int DEFAULT_DENSITY = 160;
- float f = densityDpi * (1.0f / DEFAULT_DENSITY) * fontScale * sp;
- return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
- }
-
- private static Pattern sDeviceDensityPattern =
- Pattern.compile(".*?-(l|m|tv|h|xh|xxh|xxxh|\\d+)dpi-.*?");
-
- private int getGlobalDensityDpi() throws Exception {
- final String result = getDevice().executeShellCommand("am get-config");
- final String[] lines = result.split("\n");
- if (lines.length < 1) {
- throw new IllegalStateException("Invalid config returned from device: " + result);
- }
-
- final Matcher matcher = sDeviceDensityPattern.matcher(lines[0]);
- if (!matcher.matches()) {
- throw new IllegalStateException("Invalid config returned from device: " + lines[0]);
- }
- switch (matcher.group(1)) {
- case "l": return 120;
- case "m": return 160;
- case "tv": return 213;
- case "h": return 240;
- case "xh": return 320;
- case "xxh": return 480;
- case "xxxh": return 640;
- }
- return Integer.parseInt(matcher.group(1));
- }
-
- private static final Pattern sFontSizePattern = Pattern.compile("^(.+): fontPixelSize=(.+)$");
-
- /** Read the font size in the last log line. */
- private void assertExpectedFontPixelSize(String activityName, int fontPixelSize,
- String logSeparator) throws Exception {
- final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- final Matcher matcher = sFontSizePattern.matcher(line);
- if (matcher.matches()) {
- assertEquals("Expected font pixel size does not match", fontPixelSize,
- Integer.parseInt(matcher.group(2)));
- return;
- }
- }
- fail("No fontPixelSize reported from activity " + activityName);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayLockedKeyguardTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayLockedKeyguardTests.java
deleted file mode 100644
index 1e8c5a1..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayLockedKeyguardTests.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.ActivityManagerState.STATE_STOPPED;
-import static android.server.cts.StateLogger.logE;
-
-/**
- * Display tests that require a locked keyguard.
- *
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDisplayLockedKeyguardTests
- */
-public class ActivityManagerDisplayLockedKeyguardTests extends ActivityManagerDisplayTestBase {
-
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- setLockCredential();
- }
-
- @Override
- protected void tearDown() throws Exception {
- try {
- tearDownLockCredentials();
- } catch (DeviceNotAvailableException e) {
- logE(e.getMessage());
- }
- super.tearDown();
- }
-
- /**
- * Test that virtual display content is hidden when device is locked.
- */
- public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
- if (!supportsMultiDisplay() || !isHandheld()) { return; }
-
- // Create new usual virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
-
- // Lock the device.
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_STOPPED);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false /* visible */);
-
- // Unlock and check if visibility is back.
- unlockDeviceWithCredential();
- mAmWmState.waitForKeyguardGone(mDevice);
- mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_RESUMED);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTestBase.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTestBase.java
deleted file mode 100644
index cc0a257..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTestBase.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Base class for ActivityManager display tests.
- *
- * @see ActivityManagerDisplayTests
- * @see ActivityManagerDisplayLockedKeyguardTests
- */
-public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
-
- static final int CUSTOM_DENSITY_DPI = 222;
-
- private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes";
- private static final String DUMPSYS_DISPLAY = "dumpsys display";
- private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
- private static final int INVALID_DENSITY_DPI = -1;
-
- private boolean mVirtualDisplayCreated;
- private boolean mDisplaySimulated;
-
- /** Temp storage used for parsing. */
- final LinkedList<String> mDumpLines = new LinkedList<>();
- final LinkedList<String> mAltDumpLines = new LinkedList<>();
-
- @Override
- protected void tearDown() throws Exception {
- try {
- destroyVirtualDisplays();
- destroySimulatedDisplays();
- } catch (DeviceNotAvailableException e) {
- logE(e.getMessage());
- }
- super.tearDown();
- }
-
- /** Contains the configurations applied to attached displays. */
- static final class DisplayState {
- int mDisplayId;
- String mOverrideConfig;
- String mDisplayViewportId;
-
- private DisplayState(int displayId, String overrideConfig, String uniqueId) {
- mDisplayId = displayId;
- mOverrideConfig = overrideConfig;
- mDisplayViewportId = uniqueId;
- }
-
- private int getWidth() {
- final String[] configParts = mOverrideConfig.split(" ");
- for (String part : configParts) {
- if (part.endsWith("dp") && part.startsWith("w")) {
- final String widthString = part.substring(1, part.length() - 3);
- return Integer.parseInt(widthString);
- }
- }
-
- return -1;
- }
-
- private int getHeight() {
- final String[] configParts = mOverrideConfig.split(" ");
- for (String part : configParts) {
- if (part.endsWith("dp") && part.startsWith("h")) {
- final String heightString = part.substring(1, part.length() - 3);
- return Integer.parseInt(heightString);
- }
- }
-
- return -1;
- }
-
- int getDpi() {
- final String[] configParts = mOverrideConfig.split(" ");
- for (String part : configParts) {
- if (part.endsWith("dpi")) {
- final String densityDpiString = part.substring(0, part.length() - 3);
- return Integer.parseInt(densityDpiString);
- }
- }
-
- return -1;
- }
-
- String getDisplayViewPortId() {
- return mDisplayViewportId;
- }
- }
-
- /** Contains the configurations applied to attached displays. */
- static final class ReportedDisplays {
- private static final Pattern sGlobalConfigurationPattern =
- Pattern.compile("mGlobalConfiguration: (\\{.*\\})");
- private static final Pattern sDisplayOverrideConfigurationsPattern =
- Pattern.compile("Display override configurations:");
- private static final Pattern sDisplayConfigPattern =
- Pattern.compile("(\\d+): (\\{.*\\})");
- private static final Pattern sVirtualTouchViewportPattern =
- Pattern.compile("mVirtualTouchViewports.*displayId=(\\d+), uniqueId='([^']+)', .*");
-
- String mGlobalConfig;
- private Map<Integer, DisplayState> mDisplayStates = new HashMap<>();
-
- static ReportedDisplays create(LinkedList<String> activityProcessDump,
- LinkedList<String> displayDump) {
- final ReportedDisplays result = new ReportedDisplays();
- HashMap<String, String> virtualUniqueIdMap = new HashMap<String, String>();
-
- while (!displayDump.isEmpty()) {
- final String line = displayDump.pop().trim();
-
- Matcher matcher = sVirtualTouchViewportPattern.matcher(line);
- if (matcher.matches()) {
- virtualUniqueIdMap.put(matcher.group(1), matcher.group(2));
- }
- }
-
- while (!activityProcessDump.isEmpty()) {
- final String line = activityProcessDump.pop().trim();
-
-
- Matcher actMatcher = sDisplayOverrideConfigurationsPattern.matcher(line);
- if (actMatcher.matches()) {
- log(line);
- while (ReportedDisplays.shouldContinueExtracting(activityProcessDump,
- sDisplayConfigPattern)) {
- final String displayOverrideConfigLine = activityProcessDump.pop().trim();
- log(displayOverrideConfigLine);
- actMatcher = sDisplayConfigPattern.matcher(displayOverrideConfigLine);
- actMatcher.matches();
- final String tempUniqueId = "local:" + actMatcher.group(1);
- final Integer displayId = Integer.valueOf(actMatcher.group(1));
- // Default unique ids for non virtual display.
- final String uniqueId =
- (virtualUniqueIdMap.containsKey(actMatcher.group(1)))
- ? virtualUniqueIdMap.get(actMatcher.group(1)) : tempUniqueId;
- result.mDisplayStates.put(displayId,
- new DisplayState(displayId, actMatcher.group(2), uniqueId));
- }
- continue;
- }
-
- actMatcher = sGlobalConfigurationPattern.matcher(line);
- if (actMatcher.matches()) {
- log(line);
- result.mGlobalConfig = actMatcher.group(1);
- }
- }
-
- return result;
- }
-
- /** Check if next line in dump matches the pattern and we should continue extracting. */
- static boolean shouldContinueExtracting(LinkedList<String> dump, Pattern matchingPattern) {
- if (dump.isEmpty()) {
- return false;
- }
-
- final String line = dump.peek().trim();
- return matchingPattern.matcher(line).matches();
- }
-
- DisplayState getDisplayState(int displayId) {
- return mDisplayStates.get(displayId);
- }
-
- int getNumberOfDisplays() {
- return mDisplayStates.size();
- }
-
- /** Return the display state with width, height, dpi */
- DisplayState getDisplayState(int width, int height, int dpi) {
- for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
- final DisplayState ds = entry.getValue();
- if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == dpi
- && ds.getWidth() == width && ds.getHeight() == height) {
- return ds;
- }
- }
- return null;
- }
-
- /** Return the display state with the given unique id */
- DisplayState getDisplayState(String viewportId) {
- for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
- final DisplayState ds = entry.getValue();
- if (ds.mDisplayViewportId.equals(viewportId)) {
- return ds;
- }
- }
- return null;
- }
-
- /** Check if reported state is valid. */
- boolean isValidState(int expectedDisplayCount) {
- if (mDisplayStates.size() != expectedDisplayCount) {
- return false;
- }
-
- for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) {
- final DisplayState ds = entry.getValue();
- if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == -1) {
- return false;
- }
- }
- return true;
- }
- }
-
- ReportedDisplays getDisplaysStates() throws DeviceNotAvailableException {
- // Parse dumpsys activity processes.
- final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- mDevice.executeShellCommand(DUMPSYS_ACTIVITY_PROCESSES, outputReceiver);
- String dump = outputReceiver.getOutput();
- mDumpLines.clear();
-
- Collections.addAll(mDumpLines, dump.split("\\n"));
-
- // Parse dumpsys display
- final CollectingOutputReceiver outputReceiverNew = new CollectingOutputReceiver();
- mDevice.executeShellCommand(DUMPSYS_DISPLAY, outputReceiverNew);
- String dumpNew = outputReceiverNew.getOutput();
- mAltDumpLines.clear();
-
- Collections.addAll(mAltDumpLines, dumpNew.split("\\n"));
-
- return ReportedDisplays.create(mDumpLines, mAltDumpLines);
- }
-
- /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
- protected List<ActivityManagerDisplayTests.DisplayState> findNewDisplayStates(
- ReportedDisplays oldDisplays, ReportedDisplays newDisplays) {
- final ArrayList<DisplayState> displays = new ArrayList();
-
- for (Integer displayId : newDisplays.mDisplayStates.keySet()) {
- if (!oldDisplays.mDisplayStates.containsKey(displayId)) {
- displays.add(newDisplays.getDisplayState(displayId));
- }
- }
-
- return displays;
- }
-
- /**
- * Create new virtual display.
- * @param densityDpi provide custom density for the display.
- * @param launchInSplitScreen start {@link VirtualDisplayActivity} to side from
- * {@link LaunchingActivity} on primary display.
- * @param canShowWithInsecureKeyguard allow showing content when device is showing an insecure
- * keyguard.
- * @param mustBeCreated should assert if the display was or wasn't created.
- * @param publicDisplay make display public.
- * @param resizeDisplay should resize display when surface size changes.
- * @param launchActivity should launch test activity immediately after display creation.
- * @return {@link ActivityManagerDisplayTests.DisplayState} of newly created display.
- * @throws Exception
- */
- private List<ActivityManagerDisplayTests.DisplayState> createVirtualDisplays(int densityDpi,
- boolean launchInSplitScreen, boolean canShowWithInsecureKeyguard, boolean mustBeCreated,
- boolean publicDisplay, boolean resizeDisplay, String launchActivity, int displayCount)
- throws Exception {
- // Start an activity that is able to create virtual displays.
- if (launchInSplitScreen) {
- getLaunchActivityBuilder().setToSide(true)
- .setTargetActivityName(VIRTUAL_DISPLAY_ACTIVITY).execute();
- } else {
- launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
- }
- mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY},
- false /* compareTaskAndStackBounds */);
- final ActivityManagerDisplayTests.ReportedDisplays originalDS = getDisplaysStates();
-
- // Create virtual display with custom density dpi.
- executeShellCommand(getCreateVirtualDisplayCommand(densityDpi, canShowWithInsecureKeyguard,
- publicDisplay, resizeDisplay, launchActivity, displayCount));
- mVirtualDisplayCreated = true;
-
- return assertAndGetNewDisplays(mustBeCreated ? displayCount : -1, originalDS);
- }
-
- /**
- * Simulate new display.
- * @param densityDpi provide custom density for the display.
- * @return {@link ActivityManagerDisplayTests.DisplayState} of newly created display.
- */
- private List<ActivityManagerDisplayTests.DisplayState> simulateDisplay(int densityDpi)
- throws Exception {
- final ActivityManagerDisplayTests.ReportedDisplays originalDs = getDisplaysStates();
-
- // Create virtual display with custom density dpi.
- executeShellCommand(getSimulateDisplayCommand(densityDpi));
- mDisplaySimulated = true;
-
- return assertAndGetNewDisplays(1, originalDs);
- }
-
- /**
- * Wait for desired number of displays to be created and get their properties.
- * @param newDisplayCount expected display count, -1 if display should not be created.
- * @param originalDS display states before creation of new display(s).
- */
- private List<ActivityManagerDisplayTests.DisplayState> assertAndGetNewDisplays(
- int newDisplayCount, ActivityManagerDisplayTests.ReportedDisplays originalDS)
- throws Exception {
- final int originalDisplayCount = originalDS.mDisplayStates.size();
-
- // Wait for the display(s) to be created and get configurations.
- final ActivityManagerDisplayTests.ReportedDisplays ds =
- getDisplayStateAfterChange(originalDisplayCount + newDisplayCount);
- if (newDisplayCount != -1) {
- assertEquals("New virtual display(s) must be created",
- originalDisplayCount + newDisplayCount, ds.mDisplayStates.size());
- } else {
- assertEquals("New virtual display must not be created",
- originalDisplayCount, ds.mDisplayStates.size());
- return null;
- }
-
- // Find the newly added display(s).
- final List<ActivityManagerDisplayTests.DisplayState> newDisplays
- = findNewDisplayStates(originalDS, ds);
- assertTrue("New virtual display must be created", newDisplayCount == newDisplays.size());
-
- return newDisplays;
- }
-
- /**
- * Destroy existing virtual display.
- */
- void destroyVirtualDisplays() throws Exception {
- if (mVirtualDisplayCreated) {
- executeShellCommand(getDestroyVirtualDisplayCommand());
- mVirtualDisplayCreated = false;
- }
- }
-
- /**
- * Destroy existing simulated display.
- */
- private void destroySimulatedDisplays() throws Exception {
- if (mDisplaySimulated) {
- executeShellCommand(getDestroySimulatedDisplayCommand());
- mDisplaySimulated = false;
- }
- }
-
- static class VirtualDisplayBuilder {
- private final ActivityManagerDisplayTestBase mTests;
-
- private int mDensityDpi = CUSTOM_DENSITY_DPI;
- private boolean mLaunchInSplitScreen = false;
- private boolean mCanShowWithInsecureKeyguard = false;
- private boolean mPublicDisplay = false;
- private boolean mResizeDisplay = true;
- private String mLaunchActivity = null;
- private boolean mSimulateDisplay = false;
- private boolean mMustBeCreated = true;
-
- public VirtualDisplayBuilder(ActivityManagerDisplayTestBase tests) {
- mTests = tests;
- }
-
- public VirtualDisplayBuilder setDensityDpi(int densityDpi) {
- mDensityDpi = densityDpi;
- return this;
- }
-
- public VirtualDisplayBuilder setLaunchInSplitScreen(boolean launchInSplitScreen) {
- mLaunchInSplitScreen = launchInSplitScreen;
- return this;
- }
-
- public VirtualDisplayBuilder setCanShowWithInsecureKeyguard(
- boolean canShowWithInsecureKeyguard) {
- mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
- return this;
- }
-
- public VirtualDisplayBuilder setPublicDisplay(boolean publicDisplay) {
- mPublicDisplay = publicDisplay;
- return this;
- }
-
- public VirtualDisplayBuilder setResizeDisplay(boolean resizeDisplay) {
- mResizeDisplay = resizeDisplay;
- return this;
- }
-
- public VirtualDisplayBuilder setLaunchActivity(String launchActivity) {
- mLaunchActivity = launchActivity;
- return this;
- }
-
- public VirtualDisplayBuilder setSimulateDisplay(boolean simulateDisplay) {
- mSimulateDisplay = simulateDisplay;
- return this;
- }
-
- public VirtualDisplayBuilder setMustBeCreated(boolean mustBeCreated) {
- mMustBeCreated = mustBeCreated;
- return this;
- }
-
- public DisplayState build() throws Exception {
- final List<DisplayState> displays = build(1);
- return displays != null && !displays.isEmpty() ? displays.get(0) : null;
- }
-
- public List<DisplayState> build(int count) throws Exception {
- if (mSimulateDisplay) {
- return mTests.simulateDisplay(mDensityDpi);
- }
-
- return mTests.createVirtualDisplays(mDensityDpi, mLaunchInSplitScreen,
- mCanShowWithInsecureKeyguard, mMustBeCreated, mPublicDisplay, mResizeDisplay,
- mLaunchActivity, count);
- }
- }
-
- private static String getCreateVirtualDisplayCommand(int densityDpi,
- boolean canShowWithInsecureKeyguard, boolean publicDisplay, boolean resizeDisplay,
- String launchActivity, int displayCount) {
- final StringBuilder commandBuilder
- = new StringBuilder(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY));
- commandBuilder.append(" -f 0x20000000");
- commandBuilder.append(" --es command create_display");
- if (densityDpi != INVALID_DENSITY_DPI) {
- commandBuilder.append(" --ei density_dpi ").append(densityDpi);
- }
- commandBuilder.append(" --ei count ").append(displayCount);
- commandBuilder.append(" --ez can_show_with_insecure_keyguard ")
- .append(canShowWithInsecureKeyguard);
- commandBuilder.append(" --ez public_display ").append(publicDisplay);
- commandBuilder.append(" --ez resize_display ").append(resizeDisplay);
- if (launchActivity != null) {
- commandBuilder.append(" --es launch_target_activity ").append(launchActivity);
- }
- return commandBuilder.toString();
- }
-
- private static String getDestroyVirtualDisplayCommand() {
- return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
- " --es command destroy_display";
- }
-
- private static String getSimulateDisplayCommand(int densityDpi) {
- return "settings put global overlay_display_devices 1024x768/" + densityDpi;
- }
-
- private static String getDestroySimulatedDisplayCommand() {
- return "settings delete global overlay_display_devices";
- }
-
- /** Wait for provided number of displays and report their configurations. */
- ReportedDisplays getDisplayStateAfterChange(int expectedDisplayCount)
- throws DeviceNotAvailableException {
- ReportedDisplays ds = getDisplaysStates();
-
- int retriesLeft = 5;
- while (!ds.isValidState(expectedDisplayCount) && retriesLeft-- > 0) {
- log("***Waiting for the correct number of displays...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- ds = getDisplaysStates();
- }
-
- return ds;
- }
-
- /** Checks if the device supports multi-display. */
- boolean supportsMultiDisplay() throws Exception {
- return hasDeviceFeature("android.software.activities_on_secondary_displays");
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
deleted file mode 100644
index 72a477f..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ /dev/null
@@ -1,2041 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-import android.server.displayservice.DisplayHelper;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.ActivityManagerState.STATE_PAUSED;
-import static android.server.cts.ActivityManagerState.STATE_RESUMED;
-import static android.server.cts.ActivityManagerState.STATE_STOPPED;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDisplayTests
- */
-public class ActivityManagerDisplayTests extends ActivityManagerDisplayTestBase {
- private static final String WM_SIZE = "wm size";
- private static final String WM_DENSITY = "wm density";
-
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
- private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
- private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
- private static final String SECOND_ACTIVITY_NAME = "SecondActivity";
- private static final String SECOND_ACTIVITY_NO_EMBEDDING_NAME = "SecondActivityNoEmbedding";
- private static final String THIRD_ACTIVITY_NAME = "ThirdActivity";
- private static final String VR_TEST_ACTIVITY_NAME = "VrTestActivity";
- private static final String SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME = "ShowWhenLockedAttrActivity";
- private static final String SECOND_PACKAGE_NAME = "android.server.cts.second";
- private static final String THIRD_PACKAGE_NAME = "android.server.cts.third";
- private static final String VR_UNIQUE_DISPLAY_ID =
- "virtual:android:277f1a09-b88d-4d1e-8716-796f114d080b";
- private static final String VR_STANDALONE_DEVICE_PROPERTY = "ro.boot.vr";
-
- private DisplayHelper mExternalDisplayHelper;
-
- /** Physical display metrics and overrides in the beginning of the test. */
- private ReportedDisplayMetrics mInitialDisplayMetrics;
-
- // Set on standalone VR devices to indicate that the VR virtual display is the display where 2d
- // activities are launched.
- private boolean mVrHeadset;
- private int mVrVirtualDisplayId;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mInitialDisplayMetrics = getDisplayMetrics();
- mVrHeadset = isVrHeadset();
- final DisplayState vrDisplay = getDisplaysStates().getDisplayState(VR_UNIQUE_DISPLAY_ID);
- mVrVirtualDisplayId = mVrHeadset ? vrDisplay.mDisplayId : -1;
- }
-
- @Override
- protected void tearDown() throws Exception {
- try {
- enablePersistentVrMode(false);
- restoreDisplayMetricsOverrides();
- if (mExternalDisplayHelper != null) {
- mExternalDisplayHelper.releaseDisplay();
- mExternalDisplayHelper = null;
- }
- setPrimaryDisplayState(true);
- } catch (DeviceNotAvailableException e) {
- logE(e.getMessage());
- }
- super.tearDown();
- }
-
- private void enablePersistentVrMode(boolean enabled) throws Exception {
- if (enabled) {
- executeShellCommand("setprop vr_virtualdisplay true");
- executeShellCommand("vr set-persistent-vr-mode-enabled true");
- } else {
- executeShellCommand("vr set-persistent-vr-mode-enabled false");
- executeShellCommand("setprop vr_virtualdisplay false");
- }
- }
-
- private boolean isVrHeadset() {
- try {
- if (mDevice.getProperty(VR_STANDALONE_DEVICE_PROPERTY).equals("1")) {
- return true;
- }
-
- return false;
- } catch (DeviceNotAvailableException e) {
- return false;
- }
- }
-
- private void restoreDisplayMetricsOverrides() throws Exception {
- if (mInitialDisplayMetrics.sizeOverrideSet) {
- executeShellCommand(WM_SIZE + " " + mInitialDisplayMetrics.overrideWidth + "x"
- + mInitialDisplayMetrics.overrideHeight);
- } else {
- executeShellCommand("wm size reset");
- }
- if (mInitialDisplayMetrics.densityOverrideSet) {
- executeShellCommand(WM_DENSITY + " " + mInitialDisplayMetrics.overrideDensity);
- } else {
- executeShellCommand("wm density reset");
- }
- }
-
- /**
- * Tests that the global configuration is equal to the default display's override configuration.
- */
- public void testDefaultDisplayOverrideConfiguration() throws Exception {
- final ReportedDisplays reportedDisplays = getDisplaysStates();
- assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
- final DisplayState primaryDisplay = reportedDisplays.getDisplayState(DEFAULT_DISPLAY_ID);
- assertEquals("Primary display's configuration should not be equal to global configuration.",
- reportedDisplays.mGlobalConfig, primaryDisplay.mOverrideConfig);
- }
-
- /**
- * Tests that secondary display has override configuration set.
- */
- public void testCreateVirtualDisplayWithCustomConfig() throws Exception {
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Find the density of created display.
- final int newDensityDpi = newDisplay.getDpi();
- assertEquals(CUSTOM_DENSITY_DPI, newDensityDpi);
- }
-
- /**
- * Tests that launch on secondary display is not permitted if device has the feature disabled.
- * Activities requested to be launched on a secondary display in this case should land on the
- * default display.
- */
- public void testMultiDisplayDisabled() throws Exception {
- if (supportsMultiDisplay()) {
- // Only check devices with the feature disabled.
- return;
- }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
-
- // Check that activity is on the right display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed",
- getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
- assertEquals("Front stack must be on the default display", DEFAULT_DISPLAY_ID,
- frontStack.mDisplayId);
- mAmWmState.assertFocusedStack("Focus must be on the default display", frontStackId);
- }
-
- /**
- * Tests that any new activity launch in Vr mode is in Vr display.
- */
- public void testVrActivityLaunch() throws Exception {
- if (!supportsVrMode() || !supportsMultiDisplay()) {
- // VR Mode is not supported on this device, bail from this test.
- return;
- }
-
- // Put the device in persistent vr mode.
- enablePersistentVrMode(true);
-
- // Launch the VR activity.
- launchActivity(VR_TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {VR_TEST_ACTIVITY_NAME});
- mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
-
- // Launch the non-VR 2D activity and check where it ends up.
- launchActivity(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- // Ensure that the subsequent activity is visible
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
- // Check that activity is launched in focused stack on primary display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", LAUNCHING_ACTIVITY);
- final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Launched activity must be resumed in focused stack",
- getActivityComponentName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
-
- // Check if the launch activity is in Vr virtual display id.
- final ReportedDisplays reportedDisplays = getDisplaysStates();
- assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
- final DisplayState vrDisplay = reportedDisplays.getDisplayState(VR_UNIQUE_DISPLAY_ID);
- assertNotNull("Vr mode should have a virtual display", vrDisplay);
-
- // Check if the focused activity is on this virtual stack.
- assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- // Put the device out of persistent vr mode.
- enablePersistentVrMode(false);
- }
-
- /**
- * Tests that any activity already present is re-launched in Vr display in vr mode.
- */
- public void testVrActivityReLaunch() throws Exception {
- if (!supportsVrMode() || !supportsMultiDisplay()) {
- // VR Mode is not supported on this device, bail from this test.
- return;
- }
-
- // Launch a 2D activity.
- launchActivity(LAUNCHING_ACTIVITY);
-
- // Put the device in persistent vr mode.
- enablePersistentVrMode(true);
-
- // Launch the VR activity.
- launchActivity(VR_TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {VR_TEST_ACTIVITY_NAME});
- mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
-
- // Re-launch the non-VR 2D activity and check where it ends up.
- launchActivity(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- // Ensure that the subsequent activity is visible
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
- // Check that activity is launched in focused stack on primary display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", LAUNCHING_ACTIVITY);
- final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Launched activity must be resumed in focused stack",
- getActivityComponentName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
-
- // Check if the launch activity is in Vr virtual display id.
- final ReportedDisplays reportedDisplays = getDisplaysStates();
- assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
- final DisplayState vrDisplay = reportedDisplays.getDisplayState(VR_UNIQUE_DISPLAY_ID);
- assertNotNull("Vr mode should have a virtual display", vrDisplay);
-
- // Check if the focused activity is on this virtual stack.
- assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- // Put the device out of persistent vr mode.
- enablePersistentVrMode(false);
- }
-
- private int getCurrentDefaultDisplayId() {
- return mVrHeadset ? mVrVirtualDisplayId : DEFAULT_DISPLAY_ID;
- }
-
- /**
- * Tests that any new activity launch post Vr mode is in the main display.
- */
- public void testActivityLaunchPostVr() throws Exception {
- if (!supportsVrMode() || !supportsMultiDisplay()) {
- // VR Mode is not supported on this device, bail from this test.
- return;
- }
-
- // Put the device in persistent vr mode.
- enablePersistentVrMode(true);
-
- // Launch the VR activity.
- launchActivity(VR_TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {VR_TEST_ACTIVITY_NAME});
- mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
-
- // Launch the non-VR 2D activity and check where it ends up.
- launchActivity(ALT_LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {ALT_LAUNCHING_ACTIVITY});
-
- // Ensure that the subsequent activity is visible
- mAmWmState.assertVisibility(ALT_LAUNCHING_ACTIVITY, true /* visible */);
-
- // Check that activity is launched in focused stack on primary display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", ALT_LAUNCHING_ACTIVITY);
- final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Launched activity must be resumed in focused stack",
- getActivityComponentName(ALT_LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
-
- // Check if the launch activity is in Vr virtual display id.
- final ReportedDisplays reportedDisplays = getDisplaysStates();
- assertNotNull("Global configuration must not be empty.", reportedDisplays.mGlobalConfig);
- final DisplayState vrDisplay = reportedDisplays.getDisplayState(VR_UNIQUE_DISPLAY_ID);
- assertNotNull("Vr mode should have a virtual display", vrDisplay);
-
- // Check if the focused activity is on this virtual stack.
- assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- // Put the device out of persistent vr mode.
- enablePersistentVrMode(false);
-
- // There isn't a direct launch of activity which can take an user out of persistent VR mode.
- // This sleep is to account for that delay and let device settle once it comes out of VR
- // mode.
- try {
- Thread.sleep(2000);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- // Launch the non-VR 2D activity and check where it ends up.
- launchActivity(RESIZEABLE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {RESIZEABLE_ACTIVITY_NAME});
-
- // Ensure that the subsequent activity is visible
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-
- // Check that activity is launched in focused stack on the correct display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused",
- RESIZEABLE_ACTIVITY_NAME);
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- int displayId = getCurrentDefaultDisplayId();
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(RESIZEABLE_ACTIVITY_NAME), frontStack.mResumedActivity);
- assertEquals("Front stack must be on the correct display",
- displayId, frontStack.mDisplayId);
- }
-
- public void testCreateMultipleVirtualDisplays() throws Exception {
- // Create new virtual display.
- final List<DisplayState> newDisplays = new VirtualDisplayBuilder(this).build(3);
- destroyVirtualDisplays();
- getDisplayStateAfterChange(1);
- }
-
- /**
- * Tests launching an activity on virtual display.
- */
- @Presubmit
- public void testLaunchActivityOnSecondaryDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- final String logSeparator = clearLogcat();
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- TEST_ACTIVITY_NAME);
-
- // Check that activity is on the right display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the secondary display and resumed",
- getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
- // Check that activity config corresponds to display config.
- final ReportedSizes reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY_NAME,
- logSeparator);
- assertEquals("Activity launched on secondary display must have proper configuration",
- CUSTOM_DENSITY_DPI, reportedSizes.densityDpi);
- }
-
- /**
- * Tests launching a non-resizeable activity on virtual display. It should land on the
- * default display.
- */
- public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- NON_RESIZEABLE_ACTIVITY_NAME);
-
- // Check that activity is on the right display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
- final ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the primary display and resumed",
- getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
- }
-
- /**
- * Tests launching a non-resizeable activity on virtual display while split-screen is active
- * on the primary display. It should land on the primary display and dismiss docked stack.
- */
- public void testLaunchNonResizeableActivityWithSplitScreen() throws Exception {
- if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
-
- // Start launching activity.
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- // Create new virtual display.
- final DisplayState newDisplay =
- new VirtualDisplayBuilder(this).setLaunchInSplitScreen(true).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- NON_RESIZEABLE_ACTIVITY_NAME);
-
- // Check that activity is on the right display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
- final ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the primary display and resumed",
- getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
- mAmWmState.assertDoesNotContainStack("Must not contain docked stack.", DOCKED_STACK_ID);
- }
-
- /**
- * Tests moving a non-resizeable activity to a virtual display. It should land on the default
- * display.
- */
- public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- // Launch a non-resizeable activity on a primary display.
- launchActivityInNewTask(NON_RESIZEABLE_ACTIVITY_NAME);
- // Launch a resizeable activity on new secondary display to create a new stack there.
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
-
- // Try to move the non-resizeable activity to new secondary display.
- moveActivityToStack(NON_RESIZEABLE_ACTIVITY_NAME, frontStackId);
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- NON_RESIZEABLE_ACTIVITY_NAME);
-
- // Check that activity is on the right display.
- frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
- final ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the primary display and resumed",
- getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
- }
-
- /**
- * Tests launching a non-resizeable activity on virtual display from activity there. It should
- * land on the secondary display based on the resizeability of the root activity of the task.
- */
- public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
- .build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- BROADCAST_RECEIVER_ACTIVITY);
-
- // Check that launching activity is on the secondary display.
- int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the secondary display and resumed",
- getActivityComponentName(BROADCAST_RECEIVER_ACTIVITY),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
-
- // Launch non-resizeable activity from secondary display.
- executeShellCommand("am broadcast -a trigger_broadcast --ez launch_activity true "
- + "--ez new_task true --es target_activity " + NON_RESIZEABLE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
- // Check that non-resizeable activity is on the secondary display, because of the resizeable
- // root of the task.
- frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- frontStack = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the primary display and resumed",
- getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
- }
-
- /**
- * Tests launching a non-resizeable activity on virtual display from activity there. It should
- * land on some different suitable display (usually - on the default one).
- */
- public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- LAUNCHING_ACTIVITY);
-
- // Check that launching activity is on the secondary display.
- int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be on the secondary display and resumed",
- getActivityComponentName(LAUNCHING_ACTIVITY),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
-
- // Launch non-resizeable activity from secondary display.
- getLaunchActivityBuilder().setTargetActivityName(NON_RESIZEABLE_ACTIVITY_NAME)
- .setNewTask(true).setMultipleTask(true).execute();
-
- // Check that non-resizeable activity is on the primary display.
- frontStackId = mAmWmState.getAmState().getFocusedStackId();
- frontStack = mAmWmState.getAmState().getStackById(frontStackId);
- assertFalse("Launched activity must be on a different display",
- newDisplay.mDisplayId == frontStack.mDisplayId);
- assertEquals("Launched activity must be resumed",
- getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on a just launched activity", frontStackId);
- }
-
- /**
- * Tests launching an activity on a virtual display without special permission must not be
- * allowed.
- */
- public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- final String logSeparator = clearLogcat();
-
- // Try to launch an activity and check it security exception was triggered.
- final String broadcastTarget = "-a " + SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION"
- + " -p " + SECOND_PACKAGE_NAME;
- final String includeStoppedPackagesFlag = " -f 0x00000020";
- executeShellCommand("am broadcast " + broadcastTarget
- + " --ez launch_activity true --es target_activity " + TEST_ACTIVITY_NAME
- + " --es package_name " + componentName
- + " --ei display_id " + newDisplay.mDisplayId
- + includeStoppedPackagesFlag);
-
- assertSecurityException("LaunchBroadcastReceiver", logSeparator);
-
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- assertFalse("Restricted activity must not be launched",
- mAmWmState.getAmState().containsActivity(TEST_ACTIVITY_NAME));
- }
-
- /**
- * Tests launching an activity on a virtual display without special permission must be allowed
- * for activities with same UID.
- */
- public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Try to launch an activity and check it security exception was triggered.
- final String broadcastTarget = "-a " + componentName + ".LAUNCH_BROADCAST_ACTION"
- + " -p " + componentName;
- executeShellCommand("am broadcast " + broadcastTarget
- + " --ez launch_activity true --es target_activity " + TEST_ACTIVITY_NAME
- + " --es package_name " + componentName
- + " --ei display_id " + newDisplay.mDisplayId);
-
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME);
-
- final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState()
- .getStackById(externalFocusedStackId);
- assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app", TEST_ACTIVITY_NAME);
- assertEquals("Activity launched by owner must be on external display",
- externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
- }
-
- /**
- * Tests launching an activity on virtual display and then launching another activity via shell
- * command and without specifying the display id - the second activity must appear on the
- * primary display.
- */
- @Presubmit
- public void testConsequentLaunchActivity() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- TEST_ACTIVITY_NAME);
-
- // Launch second activity without specifying display.
- launchActivity(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- // Check that activity is launched in focused stack on the correct display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", LAUNCHING_ACTIVITY);
-
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- int displayId = getCurrentDefaultDisplayId();
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity);
- assertEquals("Front stack must be on the correct display",
- displayId, frontStack.mDisplayId);
- }
-
- /**
- * Tests launching an activity on simulated display and then launching another activity from the
- * first one - it must appear on the secondary display, because it was launched from there.
- */
- @Presubmit
- public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
- .build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
- LAUNCHING_ACTIVITY);
-
- // Launch second activity from app on secondary display without specifying display id.
- getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- // Check that activity is launched in focused stack on external display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
- }
-
- /**
- * Tests launching an activity on virtual display and then launching another activity from the
- * first one - it must appear on the secondary display, because it was launched from there.
- */
- public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
- LAUNCHING_ACTIVITY);
-
- // Launch second activity from app on secondary display without specifying display id.
- getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- // Check that activity is launched in focused stack on external display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
- }
-
- /**
- * Tests launching an activity on virtual display and then launching another activity from the
- * first one with specifying the target display - it must appear on the secondary display.
- */
- public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
- LAUNCHING_ACTIVITY);
-
- // Launch second activity from app on secondary display specifying same display id.
- getLaunchActivityBuilder().setTargetActivityName(SECOND_ACTIVITY_NAME)
- .setTargetPackage(SECOND_PACKAGE_NAME)
- .setDisplayId(newDisplay.mDisplayId).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- // Check that activity is launched in focused stack on external display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", SECOND_PACKAGE_NAME,
- SECOND_ACTIVITY_NAME);
- int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME),
- frontStack.mResumedActivity);
-
- // Launch other activity with different uid and check if it has launched successfully.
- final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
- executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
- + " --ei display_id " + newDisplay.mDisplayId
- + " --es target_activity " + THIRD_ACTIVITY_NAME
- + " --es package_name " + THIRD_PACKAGE_NAME);
- mAmWmState.waitForValidState(mDevice, new String[] {THIRD_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME);
-
- // Check that activity is launched in focused stack on external display.
- mAmWmState.assertFocusedActivity("Launched activity must be focused", THIRD_PACKAGE_NAME,
- THIRD_ACTIVITY_NAME);
- frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- frontStack = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(THIRD_PACKAGE_NAME, THIRD_ACTIVITY_NAME),
- frontStack.mResumedActivity);
- }
-
- /**
- * Tests launching an activity on virtual display and then launching another activity that
- * doesn't allow embedding - it should fail with security exception.
- */
- public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be resumed",
- LAUNCHING_ACTIVITY);
-
- final String logSeparator = clearLogcat();
-
- // Launch second activity from app on secondary display specifying same display id.
- getLaunchActivityBuilder().setTargetActivityName(SECOND_ACTIVITY_NO_EMBEDDING_NAME)
- .setTargetPackage(SECOND_PACKAGE_NAME)
- .setDisplayId(newDisplay.mDisplayId).execute();
-
- assertSecurityException("ActivityLauncher", logSeparator);
- }
-
- /**
- * Tests launching an activity to secondary display from activity on primary display.
- */
- public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Start launching activity.
- launchActivity(LAUNCHING_ACTIVITY);
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
- .build();
-
- // Launch activity on secondary display from the app on primary display.
- getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME)
- .setDisplayId(newDisplay.mDisplayId).execute();
-
- // Check that activity is launched on external display.
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- TEST_ACTIVITY_NAME);
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack frontStack
- = mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Launched activity must be resumed in front stack",
- getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
- }
-
- /**
- * Tests launching activities on secondary and then on primary display to see if the stack
- * visibility is not affected.
- */
- @Presubmit
- public void testLaunchActivitiesAffectsVisibility() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Start launching activity.
- launchActivity(LAUNCHING_ACTIVITY);
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
- // Launch activity on primary display and check if it doesn't affect activity on secondary
- // display.
- getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
- mAmWmState.waitForValidState(mDevice, RESIZEABLE_ACTIVITY_NAME);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
- }
-
- /**
- * Test that move-task works when moving between displays.
- */
- @Presubmit
- public void testMoveTaskBetweenDisplays() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
- VIRTUAL_DISPLAY_ACTIVITY);
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- int displayId = getCurrentDefaultDisplayId();
- final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(defaultDisplayStackId);
- assertEquals("Focus must remain on the correct display", displayId,
- focusedStack.mDisplayId);
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display", TEST_ACTIVITY_NAME);
- int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Focused stack must be on secondary display",
- newDisplay.mDisplayId, focusedStack.mDisplayId);
-
- // Move activity from secondary display to primary.
- moveActivityToStack(TEST_ACTIVITY_NAME, defaultDisplayStackId);
- mAmWmState.waitForFocusedStack(mDevice, defaultDisplayStackId);
- mAmWmState.assertFocusedActivity("Focus must be on moved activity", TEST_ACTIVITY_NAME);
- focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Focus must return to primary display", displayId,
- focusedStack.mDisplayId);
- }
-
- /**
- * Tests launching activities on secondary display and then removing it to see if stack focus
- * is moved correctly.
- * This version launches virtual display creator to fullscreen stack in split-screen.
- */
- @Presubmit
- public void testStackFocusSwitchOnDisplayRemoved() throws Exception {
- if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
-
- // Start launching activity into docked stack.
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
- tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
- FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- /**
- * Tests launching activities on secondary display and then removing it to see if stack focus
- * is moved correctly.
- * This version launches virtual display creator to docked stack in split-screen.
- */
- public void testStackFocusSwitchOnDisplayRemoved2() throws Exception {
- if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
-
- // Setup split-screen.
- launchActivityInDockStack(RESIZEABLE_ACTIVITY_NAME);
-
- // Start launching activity into fullscreen stack.
- launchActivityInStack(LAUNCHING_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
-
- tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
- FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- /**
- * Tests launching activities on secondary display and then removing it to see if stack focus
- * is moved correctly.
- * This version works without split-screen.
- */
- public void testStackFocusSwitchOnDisplayRemoved3() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Start an activity on default display to determine default stack.
- launchActivity(BROADCAST_RECEIVER_ACTIVITY);
- final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
- // Finish probing activity.
- executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
- tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */, focusedStackId);
- }
-
- /**
- * Create a virtual display, launch a test activity there, destroy the display and check if test
- * activity is moved to a stack on the default display.
- */
- private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int defaultStackId)
- throws Exception {
- // Create new virtual display.
- final VirtualDisplayBuilder builder = new VirtualDisplayBuilder(this)
- .setPublicDisplay(true);
- if (splitScreen) {
- builder.setLaunchInSplitScreen(true);
- }
- final DisplayState newDisplay = builder.build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- if (splitScreen) {
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
- }
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display",
- TEST_ACTIVITY_NAME);
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
- // Destroy virtual display.
- destroyVirtualDisplays();
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME, defaultStackId);
- mAmWmState.assertSanity();
- mAmWmState.assertValidBounds(true /* compareTaskAndStackBounds */);
-
- // Check if the focus is switched back to primary display.
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
- mAmWmState.assertFocusedStack(
- "Default stack on primary display must be focused after display removed",
- defaultStackId);
- mAmWmState.assertFocusedActivity(
- "Focus must be switched back to activity on primary display",
- TEST_ACTIVITY_NAME);
- }
-
- /**
- * Tests launching activities on secondary display and then removing it to see if stack focus
- * is moved correctly.
- */
- public void testStackFocusSwitchOnStackEmptied() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display",
- BROADCAST_RECEIVER_ACTIVITY);
-
- // Lock the device, so that activity containers will be detached.
- sleepDevice();
-
- // Finish activity on secondary display.
- executeShellCommand(FINISH_ACTIVITY_BROADCAST);
-
- // Unlock and check if the focus is switched back to primary display.
- wakeUpAndUnlockDevice();
- mAmWmState.waitForFocusedStack(mDevice, focusedStackId);
- mAmWmState.waitForValidState(mDevice, VIRTUAL_DISPLAY_ACTIVITY);
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
- VIRTUAL_DISPLAY_ACTIVITY);
- }
-
- /**
- * Tests that input events on the primary display take focus from the virtual display.
- */
- public void testStackFocusSwitchOnTouchEvent() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY});
- mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
- VIRTUAL_DISPLAY_ACTIVITY);
-
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertFocusedActivity("Activity launched on secondary display must be focused",
- TEST_ACTIVITY_NAME);
-
- final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
- final int width = displayMetrics.getWidth();
- final int height = displayMetrics.getHeight();
- executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
-
- mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY});
- mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
- VIRTUAL_DISPLAY_ACTIVITY);
- }
-
- /** Test that shell is allowed to launch on secondary displays. */
- public void testPermissionLaunchFromShell() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- int displayId = getCurrentDefaultDisplayId();
- ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
- assertEquals("Focus must remain on the correct display", displayId,
- focusedStack.mDisplayId);
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display",
- TEST_ACTIVITY_NAME);
- final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
- assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- // Launch other activity with different uid and check it is launched on dynamic stack on
- // secondary display.
- final String startCmd = "am start -n " + SECOND_PACKAGE_NAME + "/." + SECOND_ACTIVITY_NAME
- + " --display " + newDisplay.mDisplayId;
- executeShellCommand(startCmd);
-
- mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app", SECOND_PACKAGE_NAME,
- SECOND_ACTIVITY_NAME);
- assertEquals("Activity launched by system must be on external display",
- externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
- }
-
- /** Test that launching from app that is on external display is allowed. */
- public void testPermissionLaunchFromAppOnSecondary() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
- .build();
-
- // Launch activity with different uid on secondary display.
- final String startCmd = "am start -n " + SECOND_PACKAGE_NAME + "/." + SECOND_ACTIVITY_NAME;
- final String displayTarget = " --display " + newDisplay.mDisplayId;
- executeShellCommand(startCmd + displayTarget);
-
- mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
- SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
- final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(externalFocusedStackId);
- assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- // Launch another activity with third different uid from app on secondary display and check
- // it is launched on secondary display.
- final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
- final String targetActivity = " --es target_activity " + THIRD_ACTIVITY_NAME
- + " --es package_name " + THIRD_PACKAGE_NAME
- + " --ei display_id " + newDisplay.mDisplayId;
- final String includeStoppedPackagesFlag = " -f 0x00000020";
- executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
- + targetActivity + includeStoppedPackagesFlag);
-
- mAmWmState.waitForValidState(mDevice, new String[] {THIRD_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME);
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
- THIRD_PACKAGE_NAME, THIRD_ACTIVITY_NAME);
- assertEquals("Activity launched by app on secondary display must be on that display",
- externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
- }
-
- /** Tests that an activity can launch an activity from a different UID into its own task. */
- public void testPermissionLaunchMultiUidTask() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
- .build();
-
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- // Check that the first activity is launched onto the secondary display
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- ActivityManagerState.ActivityStack frontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityComponentName(LAUNCHING_ACTIVITY),
- frontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
- // Launch an activity from a different UID into the first activity's task
- getLaunchActivityBuilder()
- .setTargetPackage(SECOND_PACKAGE_NAME)
- .setTargetActivityName(SECOND_ACTIVITY_NAME).execute();
-
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
- frontStack = mAmWmState.getAmState().getStackById(frontStackId);
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
- SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
- assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
- }
-
- /**
- * Test that launching from display owner is allowed even when the the display owner
- * doesn't have anything on the display.
- */
- public void testPermissionLaunchFromOwner() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
- assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
- focusedStack.mDisplayId);
-
- // Launch other activity with different uid on secondary display.
- final String startCmd = "am start -n " + SECOND_PACKAGE_NAME + "/." + SECOND_ACTIVITY_NAME;
- final String displayTarget = " --display " + newDisplay.mDisplayId;
- executeShellCommand(startCmd + displayTarget);
-
- mAmWmState.waitForValidState(mDevice, new String[] {SECOND_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME);
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
- SECOND_PACKAGE_NAME, SECOND_ACTIVITY_NAME);
- final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
- assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- // Check that owner uid can launch its own activity on secondary display.
- final String broadcastAction = componentName + ".LAUNCH_BROADCAST_ACTION";
- executeShellCommand("am broadcast -a " + broadcastAction + " -p " + componentName
- + " --ez launch_activity true --ez new_task true --ez multiple_task true"
- + " --ei display_id " + newDisplay.mDisplayId);
-
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY_NAME);
- mAmWmState.assertFocusedActivity("Focus must be on newly launched app", TEST_ACTIVITY_NAME);
- assertEquals("Activity launched by owner must be on external display",
- externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
- }
-
- /**
- * Test that launching from app that is not present on external display and doesn't own it to
- * that external display is not allowed.
- */
- public void testPermissionLaunchFromDifferentApp() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
- VIRTUAL_DISPLAY_ACTIVITY);
- final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- int displayId = getCurrentDefaultDisplayId();
- ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
- assertEquals("Focus must remain on the correct display", displayId,
- focusedStack.mDisplayId);
-
- // Launch activity on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display",
- TEST_ACTIVITY_NAME);
- final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
- focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
- assertEquals("Focused stack must be on secondary display", newDisplay.mDisplayId,
- focusedStack.mDisplayId);
-
- final String logSeparator = clearLogcat();
-
- // Launch other activity with different uid and check security exception is triggered.
- final String broadcastAction = SECOND_PACKAGE_NAME + ".LAUNCH_BROADCAST_ACTION";
- final String includeStoppedPackagesFlag = " -f 0x00000020";
- executeShellCommand("am broadcast -a " + broadcastAction + " -p " + SECOND_PACKAGE_NAME
- + " --ei display_id " + newDisplay.mDisplayId + includeStoppedPackagesFlag);
-
- assertSecurityException("LaunchBroadcastReceiver", logSeparator);
-
- mAmWmState.waitForValidState(mDevice, new String[] {TEST_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
- mAmWmState.assertFocusedActivity(
- "Focus must be on first activity", componentName, TEST_ACTIVITY_NAME);
- assertEquals("Focused stack must be on secondary display's stack",
- externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
- }
-
- private void assertSecurityException(String component, String logSeparator) throws Exception {
- int tries = 0;
- boolean match = false;
- final Pattern pattern = Pattern.compile(".*SecurityException launching activity.*");
- while (tries < 5 && !match) {
- String[] logs = getDeviceLogsForComponent(component, logSeparator);
- for (String line : logs) {
- Matcher m = pattern.matcher(line);
- if (m.matches()) {
- match = true;
- break;
- }
- }
- tries++;
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- }
- }
-
- assertTrue("Expected exception not found", match);
- }
-
- /**
- * Test that only private virtual display can show content with insecure keyguard.
- */
- public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
- if (!supportsMultiDisplay()) {
- return;
- }
-
- // Try to create new show-with-insecure-keyguard public virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this)
- .setPublicDisplay(true)
- .setCanShowWithInsecureKeyguard(true)
- .setMustBeCreated(false)
- .build();
-
- // Check that the display is not created.
- assertNull(newDisplay);
- }
-
- /**
- * Test that all activities that were on the private display are destroyed on display removal.
- */
- // TODO: Flaky, add to presubmit when b/63404575 is fixed.
- public void testContentDestroyOnDisplayRemoved() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new private virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
- // Launch activities on new secondary display.
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
- mAmWmState.assertFocusedActivity("Launched activity must be focused", TEST_ACTIVITY_NAME);
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
- mAmWmState.assertFocusedActivity("Launched activity must be focused",
- RESIZEABLE_ACTIVITY_NAME);
-
- // Destroy the display and check if activities are removed from system.
- final String logSeparator = clearLogcat();
- destroyVirtualDisplays();
- final String activityName1
- = ActivityManagerTestBase.getActivityComponentName(TEST_ACTIVITY_NAME);
- final String activityName2
- = ActivityManagerTestBase.getActivityComponentName(RESIZEABLE_ACTIVITY_NAME);
- final String windowName1
- = ActivityManagerTestBase.getWindowName(TEST_ACTIVITY_NAME);
- final String windowName2
- = ActivityManagerTestBase.getWindowName(RESIZEABLE_ACTIVITY_NAME);
- mAmWmState.waitForWithAmState(mDevice,
- (state) -> !state.containsActivity(activityName1)
- && !state.containsActivity(activityName2),
- "Waiting for activity to be removed");
- mAmWmState.waitForWithWmState(mDevice,
- (state) -> !state.containsWindow(windowName1)
- && !state.containsWindow(windowName2),
- "Waiting for activity window to be gone");
-
- // Check AM state.
- assertFalse("Activity from removed display must be destroyed",
- mAmWmState.getAmState().containsActivity(activityName1));
- assertFalse("Activity from removed display must be destroyed",
- mAmWmState.getAmState().containsActivity(activityName2));
- // Check WM state.
- assertFalse("Activity windows from removed display must be destroyed",
- mAmWmState.getWmState().containsWindow(windowName1));
- assertFalse("Activity windows from removed display must be destroyed",
- mAmWmState.getWmState().containsWindow(windowName2));
- // Check activity logs.
- assertActivityDestroyed(TEST_ACTIVITY_NAME, logSeparator);
- assertActivityDestroyed(RESIZEABLE_ACTIVITY_NAME, logSeparator);
- }
-
- /**
- * Test that the update of display metrics updates all its content.
- */
- @Presubmit
- public void testDisplayResize() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
- // Launch a resizeable activity on new secondary display.
- final String initialLogSeparator = clearLogcat();
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
- mAmWmState.assertFocusedActivity("Launched activity must be focused",
- RESIZEABLE_ACTIVITY_NAME);
-
- // Grab reported sizes and compute new with slight size change.
- final ReportedSizes initialSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY_NAME,
- initialLogSeparator);
-
- // Resize the docked stack, so that activity with virtual display will also be resized.
- final String logSeparator = clearLogcat();
- executeShellCommand(getResizeVirtualDisplayCommand());
-
- mAmWmState.waitForWithAmState(mDevice, amState -> {
- try {
- return readConfigChangeNumber(RESIZEABLE_ACTIVITY_NAME, logSeparator) == 1
- && amState.hasActivityState(RESIZEABLE_ACTIVITY_NAME, STATE_RESUMED);
- } catch (Exception e) {
- logE("Error waiting for valid state: " + e.getMessage());
- return false;
- }
- }, "Wait for the configuration change to happen and for activity to be resumed.");
-
- mAmWmState.computeState(mDevice, new String[] {RESIZEABLE_ACTIVITY_NAME,
- VIRTUAL_DISPLAY_ACTIVITY}, false /* compareTaskAndStackBounds */);
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true);
-
- // Check if activity in virtual display was resized properly.
- assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY_NAME, 0 /* numRelaunch */,
- 1 /* numConfigChange */, logSeparator);
-
- final ReportedSizes updatedSize = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY_NAME,
- logSeparator);
- assertTrue(updatedSize.widthDp <= initialSize.widthDp);
- assertTrue(updatedSize.heightDp <= initialSize.heightDp);
- assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
- assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2);
- }
-
- /** Read the number of configuration changes sent to activity from logs. */
- private int readConfigChangeNumber(String activityName, String logSeparator) throws Exception {
- return (new ActivityLifecycleCounts(activityName, logSeparator)).mConfigurationChangedCount;
- }
-
- /**
- * Tests that when an activity is launched with displayId specified and there is an existing
- * matching task on some other display - that task will moved to the target display.
- */
- public void testMoveToDisplayOnLaunch() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Launch activity with unique affinity, so it will the only one in its task.
- launchActivity(LAUNCHING_ACTIVITY);
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
- final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
- // Launch something to that display so that a new stack is created. We need this to be able
- // to compare task numbers in stacks later.
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
-
- final int taskNum = mAmWmState.getAmState().getStackById(defaultDisplayStackId)
- .getTasks().size();
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final int taskNumOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
- .getTasks().size();
-
- // Launch activity on new secondary display.
- // Using custom command here, because normally we add flags Intent#FLAG_ACTIVITY_NEW_TASK
- // and Intent#FLAG_ACTIVITY_MULTIPLE_TASK when launching on some specific display. We don't
- // do it here as we want an existing task to be used.
- final String launchCommand = "am start -n " + getActivityComponentName(LAUNCHING_ACTIVITY)
- + " --display " + newDisplay.mDisplayId;
- executeShellCommand(launchCommand);
- mAmWmState.waitForActivityState(mDevice, LAUNCHING_ACTIVITY, STATE_RESUMED);
-
- // Check that activity is brought to front.
- mAmWmState.assertFocusedActivity("Existing task must be brought to front",
- LAUNCHING_ACTIVITY);
- mAmWmState.assertResumedActivity("Existing task must be resumed", LAUNCHING_ACTIVITY);
-
- // Check that activity is on the right display.
- final ActivityManagerState.ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity must be moved to the secondary display",
- getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
- // Check that task has moved from primary display to secondary.
- final int taskNumFinal = mAmWmState.getAmState().getStackById(defaultDisplayStackId)
- .getTasks().size();
- mAmWmState.assertEquals("Task number in default stack must be decremented.", taskNum - 1,
- taskNumFinal);
- final int taskNumFinalOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
- .getTasks().size();
- mAmWmState.assertEquals("Task number in stack on external display must be incremented.",
- taskNumOnSecondary + 1, taskNumFinalOnSecondary);
- }
-
- /**
- * Tests that when primary display is rotated secondary displays are not affected.
- */
- public void testRotationNotAffectingSecondaryScreen() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this)
- .setResizeDisplay(false)
- .build();
- mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
-
- // Launch activity on new secondary display.
- String logSeparator = clearLogcat();
- launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display",
- RESIZEABLE_ACTIVITY_NAME);
- final ReportedSizes initialSizes = getLastReportedSizesForActivity(
- RESIZEABLE_ACTIVITY_NAME, logSeparator);
- assertNotNull("Test activity must have reported initial sizes on launch", initialSizes);
-
- // Rotate primary display and check that activity on secondary display is not affected.
- rotateAndCheckSameSizes(RESIZEABLE_ACTIVITY_NAME);
-
- // Launch activity to secondary display when primary one is rotated.
- final int initialRotation = mAmWmState.getWmState().getRotation();
- setDeviceRotation((initialRotation + 1) % 4);
-
- logSeparator = clearLogcat();
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
- mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_RESUMED);
- mAmWmState.assertFocusedActivity("Focus must be on secondary display",
- TEST_ACTIVITY_NAME);
- final ReportedSizes testActivitySizes = getLastReportedSizesForActivity(
- TEST_ACTIVITY_NAME, logSeparator);
- assertEquals("Sizes of secondary display must not change after rotation of primary display",
- initialSizes, testActivitySizes);
- }
-
- private void rotateAndCheckSameSizes(String activityName) throws Exception {
- for (int rotation = 3; rotation >= 0; --rotation) {
- final String logSeparator = clearLogcat();
- setDeviceRotation(rotation);
- final ReportedSizes rotatedSizes = getLastReportedSizesForActivity(activityName,
- logSeparator);
- assertNull("Sizes must not change after rotation", rotatedSizes);
- }
- }
-
- /**
- * Tests that task affinity does affect what display an activity is launched on but that
- * matching the task component root does.
- */
- public void testTaskMatchAcrossDisplays() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
-
- launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- // Check that activity is on the right display.
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
- executeShellCommand("am start -n " + getActivityComponentName(ALT_LAUNCHING_ACTIVITY));
- mAmWmState.waitForValidState(mDevice, new String[] {ALT_LAUNCHING_ACTIVITY},
- null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- final int displayId = getCurrentDefaultDisplayId();
- // Check that second activity gets launched on the correct display
- final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
- displayId);
- final ActivityManagerState.ActivityStack defaultDisplayFrontStack =
- mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId);
- assertEquals("Activity launched on default display must be resumed",
- getActivityComponentName(ALT_LAUNCHING_ACTIVITY),
- defaultDisplayFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on primary display",
- defaultDisplayFrontStackId);
-
- executeShellCommand("am start -n " + getActivityComponentName(LAUNCHING_ACTIVITY));
- final int stackId = mVrHeadset ? defaultDisplayFrontStackId : frontStackId;
- final int focusedStackTaskCount = mVrHeadset ? 3 : 1;
- mAmWmState.waitForFocusedStack(mDevice, stackId);
-
- // Check that the third intent is redirected to the first task
- final ActivityManagerState.ActivityStack secondFrontStack
- = mAmWmState.getAmState().getStackById(stackId);
- assertEquals("Activity launched on default display must be resumed",
- getActivityComponentName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on primary display", stackId);
- assertEquals("Focused stack must contain correct tasks",
- focusedStackTaskCount, secondFrontStack.getTasks().size());
- assertEquals("Focused task must only contain 1 activity",
- 1, secondFrontStack.getTasks().get(0).mActivities.size());
- }
-
- /**
- * Tests than a new task launched by an activity will end up on that activity's display
- * even if the focused stack is not on that activity's display.
- */
- public void testNewTaskSameDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
- .build();
-
- launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
- mAmWmState.computeState(mDevice, new String[] {BROADCAST_RECEIVER_ACTIVITY});
-
- // Check that the first activity is launched onto the secondary display
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityComponentName(BROADCAST_RECEIVER_ACTIVITY),
- firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
-
- executeShellCommand("am start -n " + getActivityComponentName(TEST_ACTIVITY_NAME));
- mAmWmState.waitForValidState(mDevice, new String[] {TEST_ACTIVITY_NAME},
- null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-
- // Check that the second activity is launched on the default display
- final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
- final ActivityManagerState.ActivityStack focusedStack
- = mAmWmState.getAmState().getStackById(focusedStackId);
- assertEquals("Activity launched on default display must be resumed",
- getActivityComponentName(TEST_ACTIVITY_NAME), focusedStack.mResumedActivity);
- // The default display id for a 2d activity launch is vr virtual display for a vr headset.
- int displayId = getCurrentDefaultDisplayId();
- assertEquals("Focus must be on the correct display", displayId,
- focusedStack.mDisplayId);
-
- executeShellCommand("am broadcast -a trigger_broadcast --ez launch_activity true "
- + "--ez new_task true --es target_activity " + LAUNCHING_ACTIVITY);
-
- // Check that the third activity ends up in a new task in the same stack as the
- // first activity
- mAmWmState.waitForValidState(mDevice, new String[] {LAUNCHING_ACTIVITY},
- null /* stackIds */, false /* compareTaskAndStackBounds */, componentName);
-
- int stackId = mAmWmState.getAmState().getFrontStackId(displayId);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", stackId);
- final ActivityManagerState.ActivityStack secondFrontStack =
- mAmWmState.getAmState().getStackById(stackId);
- assertEquals("Activity must be launched on secondary display",
- getActivityComponentName(LAUNCHING_ACTIVITY),
- secondFrontStack.mResumedActivity);
- final int taskCount = mVrHeadset ? 3 : 2;
- assertEquals("Secondary display must contain correct tasks",
- taskCount, secondFrontStack.getTasks().size());
- }
-
- /**
- * Test that display overrides apply correctly and won't be affected by display changes.
- * This sets overrides to display size and density, initiates a display changed event by locking
- * and unlocking the phone and verifies that overrides are kept.
- */
- @Presubmit
- public void testForceDisplayMetrics() throws Exception {
- launchHomeActivity();
-
- // Read initial sizes.
- final ReportedDisplayMetrics originalDisplayMetrics = getDisplayMetrics();
-
- // Apply new override values that don't match the physical metrics.
- final int overrideWidth = (int) (originalDisplayMetrics.physicalWidth * 1.5);
- final int overrideHeight = (int) (originalDisplayMetrics.physicalHeight * 1.5);
- executeShellCommand(WM_SIZE + " " + overrideWidth + "x" + overrideHeight);
- final int overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
- executeShellCommand(WM_DENSITY + " " + overrideDensity);
-
- // Check if overrides applied correctly.
- ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
- assertEquals(overrideWidth, displayMetrics.overrideWidth);
- assertEquals(overrideHeight, displayMetrics.overrideHeight);
- assertEquals(overrideDensity, displayMetrics.overrideDensity);
-
- // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
- // might update the metrics.
- sleepDevice();
- wakeUpAndUnlockDevice();
- mAmWmState.waitForHomeActivityVisible(mDevice);
-
- // Check if overrides are still applied.
- displayMetrics = getDisplayMetrics();
- assertEquals(overrideWidth, displayMetrics.overrideWidth);
- assertEquals(overrideHeight, displayMetrics.overrideHeight);
- assertEquals(overrideDensity, displayMetrics.overrideDensity);
-
- // All overrides will be cleared in tearDown.
- }
-
- /**
- * Tests than an immediate launch after new display creation is handled correctly.
- */
- public void testImmediateLaunchOnNewDisplay() throws Exception {
- if (!supportsMultiDisplay()) { return; }
-
- // Create new virtual display and immediately launch an activity on it.
- final DisplayState newDisplay = new VirtualDisplayBuilder(this)
- .setLaunchActivity(TEST_ACTIVITY_NAME).build();
-
- // Check that activity is launched and placed correctly.
- mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_RESUMED);
- mAmWmState.assertResumedActivity("Test activity must be launched on a new display",
- TEST_ACTIVITY_NAME);
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
- final ActivityManagerState.ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals("Activity launched on secondary display must be resumed",
- getActivityComponentName(TEST_ACTIVITY_NAME), firstFrontStack.mResumedActivity);
- mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
- }
-
- /**
- * Tests that turning the primary display off does not affect the activity running
- * on an external secondary display.
- */
- public void testExternalDisplayActivityTurnPrimaryOff() throws Exception {
- // Launch something on the primary display so we know there is a resumed activity there
- launchActivity(RESIZEABLE_ACTIVITY_NAME);
- waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
- "Activity launched on primary display must be resumed");
-
- final DisplayState newDisplay = createExternalVirtualDisplay(
- true /* showContentWhenLocked */);
-
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
- // Check that the activity is launched onto the external display
- waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
- "Activity launched on external display must be resumed");
-
- setPrimaryDisplayState(false);
-
- // Wait for the fullscreen stack to start sleeping, and then make sure the
- // test activity is still resumed. Note that on some devices, the top activity may go to
- // the stopped state by itself on sleep, causing the server side to believe it is still
- // paused.
- waitAndAssertActivityPausedOrStopped(RESIZEABLE_ACTIVITY_NAME,
- "Activity launched on primary display must be stopped or paused after turning off");
- waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
- "Activity launched on external display must be resumed");
- }
-
- /**
- * Tests that an activity can be launched on a secondary display while the primary
- * display is off.
- */
- public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception {
- // Launch something on the primary display so we know there is a resumed activity there
- launchActivity(RESIZEABLE_ACTIVITY_NAME);
- waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
- "Activity launched on primary display must be resumed");
-
- setPrimaryDisplayState(false);
-
- // Make sure there is no resumed activity when the primary display is off
- waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
- "Activity launched on primary display must be stopped after turning off");
- assertEquals("Unexpected resumed activity",
- 0, mAmWmState.getAmState().getResumedActivitiesCount());
-
- final DisplayState newDisplay = createExternalVirtualDisplay(
- true /* showContentWhenLocked */);
-
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
- // Check that the test activity is resumed on the external display
- waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
- "Activity launched on external display must be resumed");
- }
-
- /**
- * Tests that turning the secondary display off stops activities running on that display.
- */
- public void testExternalDisplayToggleState() throws Exception {
- final DisplayState newDisplay = createExternalVirtualDisplay(
- false /* showContentWhenLocked */);
-
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
- // Check that the test activity is resumed on the external display
- waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
- "Activity launched on external display must be resumed");
-
- mExternalDisplayHelper.turnDisplayOff();
-
- // Check that turning off the external display stops the activity
- waitAndAssertActivityStopped(TEST_ACTIVITY_NAME,
- "Activity launched on external display must be stopped after turning off");
-
- mExternalDisplayHelper.turnDisplayOn();
-
- // Check that turning on the external display resumes the activity
- waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
- "Activity launched on external display must be resumed");
- }
-
- /**
- * Tests that tapping on the primary display after showing the keyguard resumes the
- * activity on the primary display.
- */
- public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception {
- // Launch something on the primary display so we know there is a resumed activity there
- launchActivity(RESIZEABLE_ACTIVITY_NAME);
- waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
- "Activity launched on primary display must be resumed");
-
- sleepDevice();
-
- // Make sure there is no resumed activity when the primary display is off
- waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
- "Activity launched on primary display must be stopped after turning off");
- assertEquals("Unexpected resumed activity",
- 0, mAmWmState.getAmState().getResumedActivitiesCount());
-
- final DisplayState newDisplay = createExternalVirtualDisplay(
- true /* showContentWhenLocked */);
-
- launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
-
- // Check that the test activity is resumed on the external display
- waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mDisplayId,
- "Activity launched on external display must be resumed");
-
- // Unlock the device and tap on the middle of the primary display
- wakeUpDevice();
- executeShellCommand("wm dismiss-keyguard");
- final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
- final int width = displayMetrics.getWidth();
- final int height = displayMetrics.getHeight();
- executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
-
- // Check that the activity on the primary display is resumed
- waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
- "Activity launched on primary display must be resumed");
- assertEquals("Unexpected resumed activity",
- 1, mAmWmState.getAmState().getResumedActivitiesCount());
- }
-
- private void waitAndAssertActivityResumed(String activityName, int displayId, String message)
- throws Exception {
- mAmWmState.waitForActivityState(mDevice, activityName, STATE_RESUMED);
-
- final String fullActivityName = getActivityComponentName(activityName);
- assertEquals(message, fullActivityName, mAmWmState.getAmState().getResumedActivity());
- final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
- ActivityManagerState.ActivityStack firstFrontStack =
- mAmWmState.getAmState().getStackById(frontStackId);
- assertEquals(message, fullActivityName, firstFrontStack.mResumedActivity);
- assertTrue(message,
- mAmWmState.getAmState().hasActivityState(activityName, STATE_RESUMED));
- mAmWmState.assertFocusedStack("Focus must be on external display", frontStackId);
- mAmWmState.assertVisibility(activityName, true /* visible */);
- }
-
- private void waitAndAssertActivityStopped(String activityName, String message)
- throws Exception {
- waitAndAssertActivityState(activityName, message, STATE_STOPPED);
- }
-
- private void waitAndAssertActivityPausedOrStopped(String activityName, String message)
- throws Exception {
- waitAndAssertActivityState(activityName, message, STATE_PAUSED, STATE_STOPPED);
- }
-
- private void waitAndAssertActivityState(String activityName, String message, String... states)
- throws Exception {
- mAmWmState.waitForActivityState(mDevice, activityName, states);
-
- boolean stateFound = false;
-
- for (String state : states) {
- if (mAmWmState.getAmState().hasActivityState(activityName, state)) {
- stateFound = true;
- break;
- }
- }
-
- assertTrue(message, stateFound);
- }
-
- /**
- * Tests that showWhenLocked works on a secondary display.
- */
- public void testSecondaryDisplayShowWhenLocked() throws Exception {
- try {
- setLockCredential();
-
- launchActivity(TEST_ACTIVITY_NAME);
-
- final DisplayState newDisplay = createExternalVirtualDisplay(
- false /* showContentWhenLocked */);
- launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, newDisplay.mDisplayId);
-
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
-
- mAmWmState.waitForActivityState(mDevice, TEST_ACTIVITY_NAME, STATE_STOPPED);
- mAmWmState.waitForActivityState(
- mDevice, SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED);
-
- mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME });
- assertTrue("Expected resumed activity on secondary display", mAmWmState.getAmState()
- .hasActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED));
- } finally {
- tearDownLockCredentials();
- }
- }
-
- /** Get physical and override display metrics from WM. */
- private ReportedDisplayMetrics getDisplayMetrics() throws Exception {
- mDumpLines.clear();
- final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- mDevice.executeShellCommand(WM_SIZE, outputReceiver);
- mDevice.executeShellCommand(WM_DENSITY, outputReceiver);
- final String dump = outputReceiver.getOutput();
- mDumpLines.clear();
- Collections.addAll(mDumpLines, dump.split("\\n"));
- return ReportedDisplayMetrics.create(mDumpLines);
- }
-
- private static class ReportedDisplayMetrics {
- private static final Pattern sPhysicalSizePattern =
- Pattern.compile("Physical size: (\\d+)x(\\d+)");
- private static final Pattern sOverrideSizePattern =
- Pattern.compile("Override size: (\\d+)x(\\d+)");
- private static final Pattern sPhysicalDensityPattern =
- Pattern.compile("Physical density: (\\d+)");
- private static final Pattern sOverrideDensityPattern =
- Pattern.compile("Override density: (\\d+)");
-
- int physicalWidth;
- int physicalHeight;
- int physicalDensity;
-
- boolean sizeOverrideSet;
- int overrideWidth;
- int overrideHeight;
- boolean densityOverrideSet;
- int overrideDensity;
-
- /** Get width that WM operates with. */
- int getWidth() {
- return sizeOverrideSet ? overrideWidth : physicalWidth;
- }
-
- /** Get height that WM operates with. */
- int getHeight() {
- return sizeOverrideSet ? overrideHeight : physicalHeight;
- }
-
- /** Get density that WM operates with. */
- int getDensity() {
- return densityOverrideSet ? overrideDensity : physicalDensity;
- }
-
- static ReportedDisplayMetrics create(LinkedList<String> dump) {
- final ReportedDisplayMetrics result = new ReportedDisplayMetrics();
-
- boolean physicalSizeFound = false;
- boolean physicalDensityFound = false;
-
- while (!dump.isEmpty()) {
- final String line = dump.pop().trim();
-
- Matcher matcher = sPhysicalSizePattern.matcher(line);
- if (matcher.matches()) {
- physicalSizeFound = true;
- log(line);
- result.physicalWidth = Integer.parseInt(matcher.group(1));
- result.physicalHeight = Integer.parseInt(matcher.group(2));
- continue;
- }
-
- matcher = sOverrideSizePattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- result.overrideWidth = Integer.parseInt(matcher.group(1));
- result.overrideHeight = Integer.parseInt(matcher.group(2));
- result.sizeOverrideSet = true;
- continue;
- }
-
- matcher = sPhysicalDensityPattern.matcher(line);
- if (matcher.matches()) {
- physicalDensityFound = true;
- log(line);
- result.physicalDensity = Integer.parseInt(matcher.group(1));
- continue;
- }
-
- matcher = sOverrideDensityPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- result.overrideDensity = Integer.parseInt(matcher.group(1));
- result.densityOverrideSet = true;
- continue;
- }
- }
-
- assertTrue("Physical display size must be reported", physicalSizeFound);
- assertTrue("Physical display density must be reported", physicalDensityFound);
-
- return result;
- }
- }
-
- /** Assert that component received onMovedToDisplay and onConfigurationChanged callbacks. */
- private void assertMovedToDisplay(String componentName, String logSeparator) throws Exception {
- final ActivityLifecycleCounts lifecycleCounts
- = new ActivityLifecycleCounts(componentName, logSeparator);
- if (lifecycleCounts.mDestroyCount != 0) {
- fail(componentName + " has been destroyed " + lifecycleCounts.mDestroyCount
- + " time(s), wasn't expecting any");
- } else if (lifecycleCounts.mCreateCount != 0) {
- fail(componentName + " has been (re)created " + lifecycleCounts.mCreateCount
- + " time(s), wasn't expecting any");
- } else if (lifecycleCounts.mConfigurationChangedCount != 1) {
- fail(componentName + " has received "
- + lifecycleCounts.mConfigurationChangedCount
- + " onConfigurationChanged() calls, expecting " + 1);
- } else if (lifecycleCounts.mMovedToDisplayCount != 1) {
- fail(componentName + " has received "
- + lifecycleCounts.mMovedToDisplayCount
- + " onMovedToDisplay() calls, expecting " + 1);
- }
- }
-
- private static String getResizeVirtualDisplayCommand() {
- return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
- " --es command resize_display";
- }
-
- /**
- * Creates a private virtual display with the external and show with insecure
- * keyguard flags set.
- */
- private DisplayState createExternalVirtualDisplay(boolean showContentWhenLocked)
- throws Exception {
- final ReportedDisplays originalDS = getDisplaysStates();
- final int originalDisplayCount = originalDS.getNumberOfDisplays();
-
- mExternalDisplayHelper = new DisplayHelper(getDevice());
- mExternalDisplayHelper.createAndWaitForDisplay(true /* external */, showContentWhenLocked);
-
- // Wait for the virtual display to be created and get configurations.
- final ReportedDisplays ds =
- getDisplayStateAfterChange(originalDisplayCount + 1);
- assertEquals("New virtual display must be created",
- originalDisplayCount + 1, ds.getNumberOfDisplays());
-
- // Find the newly added display.
- final List<DisplayState> newDisplays = findNewDisplayStates(originalDS, ds);
- return newDisplays.get(0);
- }
-
- /** Turns the primary display on/off by pressing the power key */
- private void setPrimaryDisplayState(boolean wantOn) throws DeviceNotAvailableException {
- // Either KeyEvent.KEYCODE_WAKEUP or KeyEvent.KEYCODE_SLEEP
- int keycode = wantOn ? 224 : 223;
- getDevice().executeShellCommand("input keyevent " + keycode);
- DisplayHelper.waitForDefaultDisplayState(getDevice(), wantOn);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
deleted file mode 100644
index cf3540c..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.awt.Rectangle;
-
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerDockedStackTests
- */
-public class ActivityManagerDockedStackTests extends ActivityManagerTestBase {
-
- private static final String TEST_ACTIVITY_NAME = "TestActivity";
- private static final String FINISHABLE_ACTIVITY_NAME = "FinishableActivity";
- private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
- private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
- private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
- private static final String SINGLE_INSTANCE_ACTIVITY_NAME = "SingleInstanceActivity";
- private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
-
- private static final int TASK_SIZE = 600;
- private static final int STACK_SIZE = 300;
-
- public void testMinimumDeviceSize() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- mAmWmState.assertDeviceDefaultDisplaySize(mDevice,
- "Devices supporting multi-window must be larger than the default minimum"
- + " task size");
- }
-
- public void testStackList() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivity(TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
- mAmWmState.assertContainsStack(
- "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertDoesNotContainStack("Must not contain docked stack.", DOCKED_STACK_ID);
- }
-
- public void testDockActivity() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
- }
-
- public void testNonResizeableNotDocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(NON_RESIZEABLE_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY_NAME});
-
- mAmWmState.assertContainsStack("Must contain home stack.", HOME_STACK_ID);
- mAmWmState.assertDoesNotContainStack("Must not contain docked stack.", DOCKED_STACK_ID);
- mAmWmState.assertFrontStack(
- "Fullscreen stack must be front stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- public void testLaunchToSide() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertContainsStack(
- "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
- }
-
- public void testLaunchToSideMultiWindowCallbacks() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- // Launch two activities, one docked, one adjacent
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertContainsStack(
- "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
- // Remove the docked stack, and ensure that
- final String logSeparator = clearLogcat();
- removeStacks(DOCKED_STACK_ID);
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
- TEST_ACTIVITY_NAME, logSeparator);
- if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
- fail(TEST_ACTIVITY_NAME + " has received "
- + lifecycleCounts.mMultiWindowModeChangedCount
- + " onMultiWindowModeChanged() calls, expecting 1");
- }
- }
-
- public void testLaunchToSideAndBringToFront() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- final String[] waitForFirstVisible = new String[] {TEST_ACTIVITY_NAME};
- final String[] waitForSecondVisible = new String[] {NO_RELAUNCH_ACTIVITY_NAME};
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- // Launch activity to side.
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, waitForFirstVisible);
- int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
- TEST_ACTIVITY_NAME);
-
- // Launch another activity to side to cover first one.
- launchActivityInStack(NO_RELAUNCH_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.computeState(mDevice, waitForSecondVisible);
- int taskNumberCovered = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertEquals("Fullscreen stack must have one task added.",
- taskNumberInitial + 1, taskNumberCovered);
- mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
- NO_RELAUNCH_ACTIVITY_NAME);
-
- // Launch activity that was first launched to side. It should be brought to front.
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, waitForFirstVisible);
- int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertEquals("Task number in fullscreen stack must remain the same.",
- taskNumberCovered, taskNumberFinal);
- mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
- TEST_ACTIVITY_NAME);
- }
-
- public void testLaunchToSideMultiple() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
- final String[] waitForActivitiesVisible =
- new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
-
- // Launch activity to side.
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertNotNull("Launched to side activity must be in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
-
- // Try to launch to side same activity again.
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
- mAmWmState.assertFocusedActivity("Launched to side activity must remain in front.",
- TEST_ACTIVITY_NAME);
- mAmWmState.assertNotNull("Launched to side activity must remain in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
- }
-
- public void testLaunchToSideSingleInstance() throws Exception {
- launchTargetToSide(SINGLE_INSTANCE_ACTIVITY_NAME, false);
- }
-
- public void testLaunchToSideSingleTask() throws Exception {
- launchTargetToSide(SINGLE_TASK_ACTIVITY_NAME, false);
- }
-
- public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
- launchTargetToSide(TEST_ACTIVITY_NAME, true);
- }
-
- private void launchTargetToSide(String targetActivityName,
- boolean taskCountMustIncrement) throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
-
- final String[] waitForActivitiesVisible =
- new String[] {targetActivityName, LAUNCHING_ACTIVITY};
-
- // Launch activity to side with data.
- launchActivityToSide(true, false, targetActivityName);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- mAmWmState.assertContainsStack(
- "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertNotNull("Launched to side activity must be in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
-
- // Try to launch to side same activity again with different data.
- launchActivityToSide(true, false, targetActivityName);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- int taskNumberSecondLaunch = mAmWmState.getAmState()
- .getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTasks().size();
- if (taskCountMustIncrement) {
- mAmWmState.assertEquals("Task number must be incremented.", taskNumberInitial + 1,
- taskNumberSecondLaunch);
- } else {
- mAmWmState.assertEquals("Task number must not change.", taskNumberInitial,
- taskNumberSecondLaunch);
- }
- mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
- targetActivityName);
- mAmWmState.assertNotNull("Launched to side activity must be launched in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
-
- // Try to launch to side same activity again with no data.
- launchActivityToSide(false, false, targetActivityName);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- if (taskCountMustIncrement) {
- mAmWmState.assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
- taskNumberFinal);
- } else {
- mAmWmState.assertEquals("Task number must not change.", taskNumberSecondLaunch,
- taskNumberFinal);
- }
- mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
- targetActivityName);
- mAmWmState.assertNotNull("Launched to side activity must be launched in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
- }
-
- public void testLaunchToSideMultipleWithFlag() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
- final String[] waitForActivitiesVisible =
- new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
-
- // Launch activity to side.
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertNotNull("Launched to side activity must be in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
-
- // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
- getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
- .getTasks().size();
- mAmWmState.assertEquals("Task number must be incremented.", taskNumberInitial + 1,
- taskNumberFinal);
- mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
- TEST_ACTIVITY_NAME);
- mAmWmState.assertNotNull("Launched to side activity must remain in fullscreen stack.",
- mAmWmState.getAmState()
- .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
- }
-
- public void testRotationWhenDocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertContainsStack(
- "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
- // Rotate device single steps (90°) 0-1-2-3.
- // Each time we compute the state we implicitly assert valid bounds.
- String[] waitForActivitiesVisible =
- new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
- for (int i = 0; i < 4; i++) {
- setDeviceRotation(i);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- }
- // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for double
- // step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
- setDeviceRotation(1);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(3);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(0);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(2);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(0);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- }
-
- public void testRotationWhenDockedWhileLocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
- getLaunchActivityBuilder().setToSide(true).execute();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertSanity();
- mAmWmState.assertContainsStack(
- "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
-
- String[] waitForActivitiesVisible =
- new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
- for (int i = 0; i < 4; i++) {
- sleepDevice();
- setDeviceRotation(i);
- wakeUpAndUnlockDevice();
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- }
- }
-
- public void testRotationWhileDockMinimized() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
- assertDockMinimized();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
- mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
- HOME_STACK_ID);
-
- // Rotate device single steps (90°) 0-1-2-3.
- // Each time we compute the state we implicitly assert valid bounds in minimized mode.
- String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
- for (int i = 0; i < 4; i++) {
- setDeviceRotation(i);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- }
-
- // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for double
- // step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in minimized
- // mode.
- setDeviceRotation(1);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(3);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(0);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(2);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- setDeviceRotation(0);
- mAmWmState.computeState(mDevice, waitForActivitiesVisible);
- }
-
- public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- // Rotate the screen to check that minimize, unminimize, dismiss the docked stack and then
- // going home has the correct app transition
- for (int i = 0; i < 4; i++) {
- setDeviceRotation(i);
- launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY_NAME);
- assertDockMinimized();
-
- // Unminimize the docked stack
- pressAppSwitchButton();
- waitForDockNotMinimized();
- assertDockNotMinimized();
-
- // Dismiss the dock stack
- launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- moveActivityToStack(DOCKED_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.computeState(mDevice, new String[]{DOCKED_ACTIVITY_NAME});
-
- // Go home and check the app transition
- assertNotSame(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
- pressHomeButton();
- mAmWmState.computeState(mDevice, null);
- assertEquals(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
- }
- }
-
- public void testFinishDockActivityWhileMinimized() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStackAndMinimize(FINISHABLE_ACTIVITY_NAME);
- assertDockMinimized();
-
- runCommandAndPrintOutput("am broadcast -a 'android.server.cts.FinishableActivity.finish'");
- waitForDockNotMinimized();
- mAmWmState.assertVisibility(FINISHABLE_ACTIVITY_NAME, false);
- assertDockNotMinimized();
- }
-
- public void testDockedStackToMinimizeWhenUnlocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- sleepDevice();
- wakeUpAndUnlockDevice();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- assertDockMinimized();
- }
-
- public void testMinimizedStateWhenUnlockedAndUnMinimized() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStackAndMinimize(FINISHABLE_ACTIVITY_NAME);
- assertDockMinimized();
-
- sleepDevice();
- wakeUpAndUnlockDevice();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
- // Unminimized back to splitscreen
- pressAppSwitchButton();
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- }
-
- public void testResizeDockedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[] {DOCKED_ACTIVITY_NAME});
- launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
- resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
- mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME, DOCKED_ACTIVITY_NAME},
- false /* compareTaskAndStackBounds */);
- mAmWmState.assertContainsStack("Must contain docked stack", DOCKED_STACK_ID);
- mAmWmState.assertContainsStack("Must contain fullscreen stack",
- FULLSCREEN_WORKSPACE_STACK_ID);
- assertEquals(new Rectangle(0, 0, STACK_SIZE, STACK_SIZE),
- mAmWmState.getAmState().getStackById(DOCKED_STACK_ID).getBounds());
- mAmWmState.assertDockedTaskBounds(TASK_SIZE, TASK_SIZE, DOCKED_ACTIVITY_NAME);
- mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
- }
-
- public void testActivityLifeCycleOnResizeDockedStack() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- final String[] waitTestActivityName = new String[] {TEST_ACTIVITY_NAME};
- launchActivity(TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, waitTestActivityName);
- final Rectangle fullScreenBounds =
- mAmWmState.getWmState().getStack(FULLSCREEN_WORKSPACE_STACK_ID).getBounds();
-
- moveActivityToDockStack(TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, waitTestActivityName);
- launchActivityInStack(NO_RELAUNCH_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-
- mAmWmState.computeState(mDevice,
- new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
- final Rectangle initialDockBounds =
- mAmWmState.getWmState().getStack(DOCKED_STACK_ID).getBounds();
-
- final String logSeparator = clearLogcat();
-
- Rectangle newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, true);
- resizeDockedStack(newBounds.width, newBounds.height, newBounds.width, newBounds.height);
- mAmWmState.computeState(mDevice,
- new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
-
- // We resize twice to make sure we cross an orientation change threshold for both
- // activities.
- newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, false);
- resizeDockedStack(newBounds.width, newBounds.height, newBounds.width, newBounds.height);
- mAmWmState.computeState(mDevice,
- new String[]{TEST_ACTIVITY_NAME, NO_RELAUNCH_ACTIVITY_NAME});
- assertActivityLifecycle(TEST_ACTIVITY_NAME, true /* relaunched */, logSeparator);
- assertActivityLifecycle(NO_RELAUNCH_ACTIVITY_NAME, false /* relaunched */, logSeparator);
- }
-
- private Rectangle computeNewDockBounds(
- Rectangle fullscreenBounds, Rectangle dockBounds, boolean reduceSize) {
- final boolean inLandscape = fullscreenBounds.width > dockBounds.width;
- // We are either increasing size or reducing it.
- final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
- final Rectangle newBounds = new Rectangle(dockBounds);
- if (inLandscape) {
- // In landscape we change the width.
- newBounds.width *= sizeChangeFactor;
- } else {
- // In portrait we change the height
- newBounds.height *= sizeChangeFactor;
- }
-
- return newBounds;
- }
-
- public void testStackListOrderLaunchDockedActivity() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
-
- launchActivityInDockStack(TEST_ACTIVITY_NAME);
- mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY_NAME});
-
- final int homeStackIndex = mAmWmState.getStackPosition(HOME_STACK_ID);
- final int recentsStackIndex = mAmWmState.getStackPosition(RECENTS_STACK_ID);
- assertTrue("Recents stack should be on top of home stack",
- recentsStackIndex < homeStackIndex);
- }
-
- private void launchActivityInDockStackAndMinimize(String activityName) throws Exception {
- launchActivityInDockStack(activityName);
- pressHomeButton();
- waitForDockMinimized();
- }
-
- private void assertDockMinimized() {
- assertTrue(mAmWmState.getWmState().isDockedStackMinimized());
- }
-
- private void assertDockNotMinimized() {
- assertFalse(mAmWmState.getWmState().isDockedStackMinimized());
- }
-
- private void waitForDockMinimized() throws Exception {
- mAmWmState.waitForWithWmState(mDevice, state -> state.isDockedStackMinimized(),
- "***Waiting for Dock stack to be minimized");
- }
-
- private void waitForDockNotMinimized() throws Exception {
- mAmWmState.waitForWithWmState(mDevice, state -> !state.isDockedStackMinimized(),
- "***Waiting for Dock stack to not be minimized");
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
deleted file mode 100644
index a1d76c8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-
-import java.awt.Rectangle;
-import java.util.ArrayList;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerFreeformStackTests
- */
-public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
-
- private static final String TEST_ACTIVITY = "TestActivity";
- private static final int TEST_TASK_OFFSET = 20;
- private static final int TEST_TASK_OFFSET_2 = 100;
- private static final int TEST_TASK_SIZE_1 = 900;
- private static final int TEST_TASK_SIZE_2 = TEST_TASK_SIZE_1 * 2;
- private static final int TEST_TASK_SIZE_DP_1 = 220;
- private static final int TEST_TASK_SIZE_DP_2 = TEST_TASK_SIZE_DP_1 * 2;
-
- // NOTE: Launching the FreeformActivity will automatically launch the TestActivity
- // with bounds (0, 0, 900, 900)
- private static final String FREEFORM_ACTIVITY = "FreeformActivity";
- private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
- private static final String NO_RELAUNCH_ACTIVITY = "NoRelaunchActivity";
-
- public void testFreeformWindowManagementSupport() throws Exception {
-
- launchActivityInStack(FREEFORM_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-
- mAmWmState.computeState(mDevice, new String[] {FREEFORM_ACTIVITY, TEST_ACTIVITY});
-
- if (!supportsFreeform()) {
- mAmWmState.assertDoesNotContainStack(
- "Must not contain freeform stack.", FREEFORM_WORKSPACE_STACK_ID);
- return;
- }
-
- mAmWmState.assertFrontStack(
- "Freeform stack must be the front stack.", FREEFORM_WORKSPACE_STACK_ID);
- mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
- mAmWmState.assertFocusedActivity(
- TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
- assertEquals(new Rectangle(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
- mAmWmState.getAmState().getTaskByActivityName(TEST_ACTIVITY).getBounds());
- }
-
- public void testNonResizeableActivityHasFullDisplayBounds() throws Exception {
- launchActivityInStack(NON_RESIZEABLE_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY});
-
- final ActivityTask task =
- mAmWmState.getAmState().getTaskByActivityName(NON_RESIZEABLE_ACTIVITY);
- final ActivityStack stack = mAmWmState.getAmState().getStackById(task.mStackId);
-
- if (task.isFullscreen()) {
- // If the task is on the fullscreen stack, then we know that it will have bounds that
- // fill the entire display.
- return;
- }
-
- // If the task is not on the fullscreen stack, then compare the task bounds to the display
- // bounds.
- assertEquals(mAmWmState.getWmState().getDisplay(stack.mDisplayId).getDisplayRect(),
- task.getBounds());
- }
-
- public void testActivityLifeCycleOnResizeFreeformTask() throws Exception {
- launchActivityInStack(TEST_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
- launchActivityInStack(NO_RELAUNCH_ACTIVITY, FREEFORM_WORKSPACE_STACK_ID);
-
- mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY});
-
- if (!supportsFreeform()) {
- mAmWmState.assertDoesNotContainStack(
- "Must not contain freeform stack.", FREEFORM_WORKSPACE_STACK_ID);
- return;
- }
-
- final int displayId = mAmWmState.getAmState().getStackById(
- ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID).mDisplayId;
- final int densityDpi =
- mAmWmState.getWmState().getDisplay(displayId).getDpi();
- final int testTaskSize1 =
- ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_1, densityDpi);
- final int testTaskSize2 =
- ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_2, densityDpi);
-
- resizeActivityTask(TEST_ACTIVITY,
- TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize1, testTaskSize2);
- resizeActivityTask(NO_RELAUNCH_ACTIVITY,
- TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize1, testTaskSize2);
-
- mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY});
-
- final String logSeparator = clearLogcat();
- resizeActivityTask(TEST_ACTIVITY,
- TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize2, testTaskSize1);
- resizeActivityTask(NO_RELAUNCH_ACTIVITY,
- TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize2, testTaskSize1);
- mAmWmState.computeState(mDevice, new String[]{TEST_ACTIVITY, NO_RELAUNCH_ACTIVITY});
-
- assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
- assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
deleted file mode 100644
index e6df4b2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import java.lang.Exception;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.Assert;
-
-import java.awt.Rectangle;
-import android.server.cts.WindowManagerState.WindowState;
-import android.server.cts.WindowManagerState.Display;
-
-import static android.server.cts.ActivityAndWindowManagersState.dpToPx;
-import static com.android.ddmlib.Log.LogLevel.INFO;
-
-import com.android.tradefed.log.LogUtil.CLog;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerManifestLayoutTests
- */
-public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
-
- // Test parameters
- private static final int DEFAULT_WIDTH_DP = 240;
- private static final int DEFAULT_HEIGHT_DP = 160;
- private static final float DEFAULT_WIDTH_FRACTION = 0.25f;
- private static final float DEFAULT_HEIGHT_FRACTION = 0.35f;
- private static final int MIN_WIDTH_DP = 100;
- private static final int MIN_HEIGHT_DP = 80;
-
- private static final int GRAVITY_VER_CENTER = 0x01;
- private static final int GRAVITY_VER_TOP = 0x02;
- private static final int GRAVITY_VER_BOTTOM = 0x04;
- private static final int GRAVITY_HOR_CENTER = 0x10;
- private static final int GRAVITY_HOR_LEFT = 0x20;
- private static final int GRAVITY_HOR_RIGHT = 0x40;
-
- private List<WindowState> mTempWindowList = new ArrayList();
- private Display mDisplay;
- private WindowState mWindowState;
-
- public void testGravityAndDefaultSizeTopLeft() throws Exception {
- testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/);
- }
-
- public void testGravityAndDefaultSizeTopRight() throws Exception {
- testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/);
- }
-
- public void testGravityAndDefaultSizeBottomLeft() throws Exception {
- testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/);
- }
-
- public void testGravityAndDefaultSizeBottomRight() throws Exception {
- testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/);
- }
-
- public void testMinimalSizeFreeform() throws Exception {
- if (!supportsFreeform()) {
- CLog.logAndDisplay(INFO, "Skipping test: no freeform support");
- return;
- }
- testMinimalSize(FREEFORM_WORKSPACE_STACK_ID);
- }
-
- public void testMinimalSizeDocked() throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
- return;
- }
- testMinimalSize(DOCKED_STACK_ID);
- }
-
- private void testMinimalSize(int stackId) throws Exception {
- final String activityName = "BottomRightLayoutActivity";
-
- // Issue command to resize to <0,0,1,1>. We expect the size to be floored at
- // MIN_WIDTH_DPxMIN_HEIGHT_DP.
- if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
- launchActivityInStack(activityName, stackId);
- resizeActivityTask(activityName, 0, 0, 1, 1);
- } else { // stackId == DOCKED_STACK_ID
- launchActivityInDockStack(activityName);
- resizeDockedStack(1, 1, 1, 1);
- }
- getDisplayAndWindowState(activityName, false);
-
- final int minWidth = dpToPx(MIN_WIDTH_DP, mDisplay.getDpi());
- final int minHeight = dpToPx(MIN_HEIGHT_DP, mDisplay.getDpi());
- final Rectangle containingRect = mWindowState.getContainingFrame();
-
- Assert.assertEquals("Min width is incorrect", minWidth, containingRect.width);
- Assert.assertEquals("Min height is incorrect", minHeight, containingRect.height);
- }
-
- private void testLayout(
- int vGravity, int hGravity, boolean fraction) throws Exception {
- if (!supportsFreeform()) {
- CLog.logAndDisplay(INFO, "Skipping test: no freeform support");
- return;
- }
-
- final String activityName = (vGravity == GRAVITY_VER_TOP ? "Top" : "Bottom")
- + (hGravity == GRAVITY_HOR_LEFT ? "Left" : "Right") + "LayoutActivity";
-
- // Launch in freeform stack
- launchActivityInStack(activityName, FREEFORM_WORKSPACE_STACK_ID);
-
- getDisplayAndWindowState(activityName, true);
-
- final Rectangle containingRect = mWindowState.getContainingFrame();
- final Rectangle appRect = mDisplay.getAppRect();
- final int expectedWidthPx, expectedHeightPx;
- // Evaluate the expected window size in px. If we're using fraction dimensions,
- // calculate the size based on the app rect size. Otherwise, convert the expected
- // size in dp to px.
- if (fraction) {
- expectedWidthPx = (int) (appRect.width * DEFAULT_WIDTH_FRACTION);
- expectedHeightPx = (int) (appRect.height * DEFAULT_HEIGHT_FRACTION);
- } else {
- final int densityDpi = mDisplay.getDpi();
- expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
- expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
- }
-
- verifyFrameSizeAndPosition(
- vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
- }
-
- private void getDisplayAndWindowState(String activityName, boolean checkFocus)
- throws Exception {
- final String windowName = getWindowName(activityName);
-
- mAmWmState.computeState(mDevice, new String[] {activityName});
-
- if (checkFocus) {
- mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
- } else {
- mAmWmState.assertVisibility(activityName, true);
- }
-
- mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mTempWindowList);
-
- Assert.assertEquals("Should have exactly one window state for the activity.",
- 1, mTempWindowList.size());
-
- mWindowState = mTempWindowList.get(0);
- Assert.assertNotNull("Should have a valid window", mWindowState);
-
- mDisplay = mAmWmState.getWmState().getDisplay(mWindowState.getDisplayId());
- Assert.assertNotNull("Should be on a display", mDisplay);
- }
-
- private void verifyFrameSizeAndPosition(
- int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
- Rectangle containingFrame, Rectangle parentFrame) {
- Assert.assertEquals("Width is incorrect", expectedWidthPx, containingFrame.width);
- Assert.assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height);
-
- if (vGravity == GRAVITY_VER_TOP) {
- Assert.assertEquals("Should be on the top", parentFrame.y, containingFrame.y);
- } else if (vGravity == GRAVITY_VER_BOTTOM) {
- Assert.assertEquals("Should be on the bottom",
- parentFrame.y + parentFrame.height, containingFrame.y + containingFrame.height);
- }
-
- if (hGravity == GRAVITY_HOR_LEFT) {
- Assert.assertEquals("Should be on the left", parentFrame.x, containingFrame.x);
- } else if (hGravity == GRAVITY_HOR_RIGHT){
- Assert.assertEquals("Should be on the right",
- parentFrame.x + parentFrame.width, containingFrame.x + containingFrame.width);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
deleted file mode 100644
index 5f63580..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerPinnedStackTests.java
+++ /dev/null
@@ -1,1338 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.ActivityManagerState.STATE_STOPPED;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-
-import java.awt.Rectangle;
-import java.lang.Exception;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests
- */
-public class ActivityManagerPinnedStackTests extends ActivityManagerTestBase {
- private static final String TEST_ACTIVITY = "TestActivity";
- private static final String TEST_ACTIVITY_WITH_SAME_AFFINITY = "TestActivityWithSameAffinity";
- private static final String TRANSLUCENT_TEST_ACTIVITY = "TranslucentTestActivity";
- private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
- private static final String RESUME_WHILE_PAUSING_ACTIVITY = "ResumeWhilePausingActivity";
- private static final String PIP_ACTIVITY = "PipActivity";
- private static final String PIP_ACTIVITY2 = "PipActivity2";
- private static final String PIP_ACTIVITY_WITH_SAME_AFFINITY = "PipActivityWithSameAffinity";
- private static final String ALWAYS_FOCUSABLE_PIP_ACTIVITY = "AlwaysFocusablePipActivity";
- private static final String LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY =
- "LaunchIntoPinnedStackPipActivity";
- private static final String LAUNCH_ENTER_PIP_ACTIVITY = "LaunchEnterPipActivity";
- private static final String PIP_ON_STOP_ACTIVITY = "PipOnStopActivity";
-
- private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
- private static final String EXTRA_ENTER_PIP = "enter_pip";
- private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
- "enter_pip_aspect_ratio_numerator";
- private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
- "enter_pip_aspect_ratio_denominator";
- private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
- private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
- private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
- "set_aspect_ratio_with_delay_numerator";
- private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
- "set_aspect_ratio_with_delay_denominator";
- private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
- private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
- private static final String EXTRA_START_ACTIVITY = "start_activity";
- private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
- private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
- private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
- private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
-
- private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
- "android.server.cts.PipActivity.enter_pip";
- private static final String PIP_ACTIVITY_ACTION_MOVE_TO_BACK =
- "android.server.cts.PipActivity.move_to_back";
- private static final String PIP_ACTIVITY_ACTION_EXPAND_PIP =
- "android.server.cts.PipActivity.expand_pip";
- private static final String PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION =
- "android.server.cts.PipActivity.set_requested_orientation";
- private static final String PIP_ACTIVITY_ACTION_FINISH =
- "android.server.cts.PipActivity.finish";
- private static final String TEST_ACTIVITY_ACTION_FINISH =
- "android.server.cts.TestActivity.finish_self";
-
- private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
- private static final int APP_OPS_MODE_ALLOWED = 0;
- private static final int APP_OPS_MODE_IGNORED = 1;
- private static final int APP_OPS_MODE_ERRORED = 2;
-
- private static final int ROTATION_0 = 0;
- private static final int ROTATION_90 = 1;
- private static final int ROTATION_180 = 2;
- private static final int ROTATION_270 = 3;
-
- // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
- private static final int ORIENTATION_LANDSCAPE = 0;
- // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
- private static final int ORIENTATION_PORTRAIT = 1;
-
- private static final float FLOAT_COMPARE_EPSILON = 0.005f;
-
- // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
- private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
- private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
- private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
- // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
- private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
- private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
- private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
-
- public void testMinimumDeviceSize() throws Exception {
- if (!supportsPip()) return;
-
- mAmWmState.assertDeviceDefaultDisplaySize(mDevice,
- "Devices supporting picture-in-picture must be larger than the default minimum"
- + " task size");
- }
-
- public void testEnterPictureInPictureMode() throws Exception {
- pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"), PIP_ACTIVITY,
- false /* moveTopToPinnedStack */, false /* isFocusable */);
- }
-
- public void testMoveTopActivityToPinnedStack() throws Exception {
- pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY,
- true /* moveTopToPinnedStack */, false /* isFocusable */);
- }
-
- public void testAlwaysFocusablePipActivity() throws Exception {
- pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
- ALWAYS_FOCUSABLE_PIP_ACTIVITY, false /* moveTopToPinnedStack */,
- true /* isFocusable */);
- }
-
- public void testLaunchIntoPinnedStack() throws Exception {
- pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
- ALWAYS_FOCUSABLE_PIP_ACTIVITY, false /* moveTopToPinnedStack */,
- true /* isFocusable */);
- }
-
- public void testNonTappablePipActivity() throws Exception {
- if (!supportsPip()) return;
-
- // Launch the tap-to-finish activity at a specific place
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_TAP_TO_FINISH, "true");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
-
- // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
- // not passed down to the top task
- tapToFinishPip();
- mAmWmState.computeState(mDevice, new String[] {PIP_ACTIVITY},
- false /* compareTaskAndStackBounds */);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
- }
-
- public void testPinnedStackDefaultBounds() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a PIP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
- setDeviceRotation(ROTATION_0);
- WindowManagerState wmState = mAmWmState.getWmState();
- wmState.computeState(mDevice);
- Rectangle defaultPipBounds = wmState.getDefaultPinnedStackBounds();
- Rectangle stableBounds = wmState.getStableBounds();
- assertTrue(defaultPipBounds.width > 0 && defaultPipBounds.height > 0);
- assertTrue(stableBounds.contains(defaultPipBounds));
-
- setDeviceRotation(ROTATION_90);
- wmState = mAmWmState.getWmState();
- wmState.computeState(mDevice);
- defaultPipBounds = wmState.getDefaultPinnedStackBounds();
- stableBounds = wmState.getStableBounds();
- assertTrue(defaultPipBounds.width > 0 && defaultPipBounds.height > 0);
- assertTrue(stableBounds.contains(defaultPipBounds));
- setDeviceRotation(ROTATION_0);
- }
-
- public void testPinnedStackMovementBounds() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a PIP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
- setDeviceRotation(ROTATION_0);
- WindowManagerState wmState = mAmWmState.getWmState();
- wmState.computeState(mDevice);
- Rectangle pipMovementBounds = wmState.getPinnedStackMomentBounds();
- Rectangle stableBounds = wmState.getStableBounds();
- assertTrue(pipMovementBounds.width > 0 && pipMovementBounds.height > 0);
- assertTrue(stableBounds.contains(pipMovementBounds));
-
- setDeviceRotation(ROTATION_90);
- wmState = mAmWmState.getWmState();
- wmState.computeState(mDevice);
- pipMovementBounds = wmState.getPinnedStackMomentBounds();
- stableBounds = wmState.getStableBounds();
- assertTrue(pipMovementBounds.width > 0 && pipMovementBounds.height > 0);
- assertTrue(stableBounds.contains(pipMovementBounds));
- setDeviceRotation(ROTATION_0);
- }
-
- public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
- if (!supportsPip()) return;
-
- final WindowManagerState wmState = mAmWmState.getWmState();
-
- // Launch an activity into the pinned stack
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_TAP_TO_FINISH, "true");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-
- // Get the display dimensions
- WindowManagerState.WindowState windowState = getWindowState(PIP_ACTIVITY);
- WindowManagerState.Display display = wmState.getDisplay(windowState.getDisplayId());
- Rectangle displayRect = display.getDisplayRect();
-
- // Move the pinned stack offscreen
- String moveStackOffscreenCommand = String.format("am stack resize 4 %d %d %d %d",
- displayRect.width - 200, 0, displayRect.width + 200, 500);
- executeShellCommand(moveStackOffscreenCommand);
-
- // Ensure that the surface insets are not negative
- windowState = getWindowState(PIP_ACTIVITY);
- Rectangle contentInsets = windowState.getContentInsets();
- assertTrue(contentInsets.x >= 0 && contentInsets.y >= 0 && contentInsets.width >= 0 &&
- contentInsets.height >= 0);
- }
-
- public void testPinnedStackInBoundsAfterRotation() throws Exception {
- if (!supportsPip()) return;
-
- // Launch an activity into the pinned stack
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_TAP_TO_FINISH, "true");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-
- // Ensure that the PIP stack is fully visible in each orientation
- setDeviceRotation(ROTATION_0);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- setDeviceRotation(ROTATION_90);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- setDeviceRotation(ROTATION_180);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- setDeviceRotation(ROTATION_270);
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- setDeviceRotation(ROTATION_0);
- }
-
- public void testEnterPipToOtherOrientation() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a portrait only app on the fullscreen stack
- launchActivity(TEST_ACTIVITY,
- EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
- // Launch the PiP activity fixed as landscape
- launchActivity(PIP_ACTIVITY,
- EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE));
- // Enter PiP, and assert that the PiP is within bounds now that the device is back in
- // portrait
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
- assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
- }
-
- public void testEnterPipAspectRatioMin() throws Exception {
- testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
- }
-
- public void testEnterPipAspectRatioMax() throws Exception {
- testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
- }
-
- private void testEnterPipAspectRatio(int num, int denom) throws Exception {
- if (!supportsPip()) return;
-
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
- EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
- assertPinnedStackExists();
-
- // Assert that we have entered PIP and that the aspect ratio is correct
- Rectangle pinnedStackBounds =
- mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
- assertTrue(floatEquals((float) pinnedStackBounds.width / pinnedStackBounds.height,
- (float) num / denom));
- }
-
- public void testResizePipAspectRatioMin() throws Exception {
- testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
- }
-
- public void testResizePipAspectRatioMax() throws Exception {
- testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
- }
-
- private void testResizePipAspectRatio(int num, int denom) throws Exception {
- if (!supportsPip()) return;
-
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
- EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
- assertPinnedStackExists();
-
- // Hacky, but we need to wait for the enterPictureInPicture animation to complete and
- // the resize to be called before we can check the pinned stack bounds
- final boolean[] result = new boolean[1];
- mAmWmState.waitForWithAmState(mDevice, (state) -> {
- Rectangle pinnedStackBounds = state.getStackById(PINNED_STACK_ID).getBounds();
- boolean isValidAspectRatio = floatEquals(
- (float) pinnedStackBounds.width / pinnedStackBounds.height,
- (float) num / denom);
- result[0] = isValidAspectRatio;
- return isValidAspectRatio;
- }, "Waiting for pinned stack to be resized");
- assertTrue(result[0]);
- }
-
- public void testEnterPipExtremeAspectRatioMin() throws Exception {
- testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
- BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
- }
-
- public void testEnterPipExtremeAspectRatioMax() throws Exception {
- testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
- MAX_ASPECT_RATIO_DENOMINATOR);
- }
-
- private void testEnterPipExtremeAspectRatio(int num, int denom) throws Exception {
- if (!supportsPip()) return;
-
- // Assert that we could not create a pinned stack with an extreme aspect ratio
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
- EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
- assertPinnedStackDoesNotExist();
- }
-
- public void testSetPipExtremeAspectRatioMin() throws Exception {
- testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
- BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
- }
-
- public void testSetPipExtremeAspectRatioMax() throws Exception {
- testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
- MAX_ASPECT_RATIO_DENOMINATOR);
- }
-
- private void testSetPipExtremeAspectRatio(int num, int denom) throws Exception {
- if (!supportsPip()) return;
-
- // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
- // fails (the aspect ratio remains the same)
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
- Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
- EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
- Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
- EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
- EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
- assertPinnedStackExists();
- Rectangle pinnedStackBounds =
- mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
- assertTrue(floatEquals((float) pinnedStackBounds.width / pinnedStackBounds.height,
- (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR));
- }
-
- public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
- if (!supportsPip()) return;
-
- // Launch the bottom pip activity
- launchActivity(PIP_ON_STOP_ACTIVITY);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
-
- // Wait for the bottom pip activity to be stopped
- mAmWmState.waitForActivityState(mDevice, PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
-
- // Assert that there is no pinned stack (that enterPictureInPicture() failed)
- assertPinnedStackDoesNotExist();
- }
-
- public void testAutoEnterPictureInPicture() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a test activity so that we're not over home
- launchActivity(TEST_ACTIVITY);
-
- // Launch the PIP activity on pause
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
- assertPinnedStackDoesNotExist();
-
- // Go home and ensure that there is a pinned stack
- launchHomeActivity();
- assertPinnedStackExists();
- }
-
- public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a test activity so that we're not over home
- launchActivity(TEST_ACTIVITY);
-
- // Launch the PIP activity on pause, and have it start another activity on
- // top of itself. Wait for the new activity to be visible and ensure that the pinned stack
- // was not created in the process
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP_ON_PAUSE, "true",
- EXTRA_START_ACTIVITY, getActivityComponentName(NON_RESIZEABLE_ACTIVITY));
- mAmWmState.computeState(mDevice, new String[] {NON_RESIZEABLE_ACTIVITY},
- false /* compareTaskAndStackBounds */);
- assertPinnedStackDoesNotExist();
-
- // Go home while the pip activity is open and ensure the previous activity is not PIPed
- launchHomeActivity();
- assertPinnedStackDoesNotExist();
- }
-
- public void testAutoEnterPictureInPictureFinish() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a test activity so that we're not over home
- launchActivity(TEST_ACTIVITY);
-
- // Launch the PIP activity on pause, and set it to finish itself after
- // some period. Wait for the previous activity to be visible, and ensure that the pinned
- // stack was not created in the process
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP_ON_PAUSE, "true",
- EXTRA_FINISH_SELF_ON_RESUME, "true");
- assertPinnedStackDoesNotExist();
- }
-
- public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
- if (!supportsPip()) return;
-
- // Launch the PIP activity on pause, and set the aspect ratio
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP_ON_PAUSE, "true",
- EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
- EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
-
- // Go home while the pip activity is open to trigger auto-PIP
- launchHomeActivity();
- assertPinnedStackExists();
-
- // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
- // and before we can check the pinned stack bounds
- final boolean[] result = new boolean[1];
- mAmWmState.waitForWithAmState(mDevice, (state) -> {
- Rectangle pinnedStackBounds = state.getStackById(PINNED_STACK_ID).getBounds();
- boolean isValidAspectRatio = floatEquals(
- (float) pinnedStackBounds.width / pinnedStackBounds.height,
- (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
- result[0] = isValidAspectRatio;
- return isValidAspectRatio;
- }, "Waiting for pinned stack to be resized");
- assertTrue(result[0]);
- }
-
- public void testAutoEnterPictureInPictureOverPip() throws Exception {
- if (!supportsPip()) return;
-
- // Launch another PIP activity
- launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
-
- // Launch the PIP activity on pause
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
-
- // Go home while the PIP activity is open to trigger auto-enter PIP
- launchHomeActivity();
- assertPinnedStackExists();
-
- // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
- // still the first activity
- final ActivityStack pinnedStack = mAmWmState.getAmState().getStackById(PINNED_STACK_ID);
- assertTrue(pinnedStack.getTasks().size() == 1);
- assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
- ALWAYS_FOCUSABLE_PIP_ACTIVITY)));
- }
-
- public void testDisallowMultipleTasksInPinnedStack() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a test activity so that we have multiple fullscreen tasks
- launchActivity(TEST_ACTIVITY);
-
- // Launch first PIP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
- // Launch second PIP activity
- launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
-
- final ActivityStack pinnedStack = mAmWmState.getAmState().getStackById(PINNED_STACK_ID);
- assertEquals(1, pinnedStack.getTasks().size());
-
- assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
- PIP_ACTIVITY2)));
-
- final ActivityStack fullScreenStack = mAmWmState.getAmState().getStackById(
- FULLSCREEN_WORKSPACE_STACK_ID);
- assertTrue(fullScreenStack.getBottomTask().mRealActivity.equals(getActivityComponentName(
- PIP_ACTIVITY)));
- }
-
- public void testPipUnPipOverHome() throws Exception {
- if (!supportsPip()) return;
-
- // Go home
- launchHomeActivity();
- // Launch an auto pip activity
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_REENTER_PIP_ON_EXIT, "true");
- assertPinnedStackExists();
-
- // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
- launchActivity(PIP_ACTIVITY);
- mAmWmState.waitForWithAmState(mDevice, (amState) -> {
- return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
- }, "Waiting for PIP to exit to fullscreen");
- mAmWmState.waitForWithAmState(mDevice, (amState) -> {
- return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == PINNED_STACK_ID;
- }, "Waiting to re-enter PIP");
- mAmWmState.assertFocusedStack("Expected home stack focused", HOME_STACK_ID);
- }
-
- public void testPipUnPipOverApp() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a test activity so that we're not over home
- launchActivity(TEST_ACTIVITY);
-
- // Launch an auto pip activity
- launchActivity(PIP_ACTIVITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_REENTER_PIP_ON_EXIT, "true");
- assertPinnedStackExists();
-
- // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
- launchActivity(PIP_ACTIVITY);
- mAmWmState.waitForWithAmState(mDevice, (amState) -> {
- return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == FULLSCREEN_WORKSPACE_STACK_ID;
- }, "Waiting for PIP to exit to fullscreen");
- mAmWmState.waitForWithAmState(mDevice, (amState) -> {
- return amState.getFrontStackId(DEFAULT_DISPLAY_ID) == PINNED_STACK_ID;
- }, "Waiting to re-enter PIP");
- mAmWmState.assertFocusedStack("Expected fullscreen stack focused",
- FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- public void testRemovePipWithNoFullscreenStack() throws Exception {
- if (!supportsPip()) return;
-
- // Start with a clean slate, remove all the stacks but home
- removeStacks(ALL_STACK_IDS_BUT_HOME);
-
- // Launch a pip activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Remove the stack and ensure that the task is now in the fullscreen stack (when no
- // fullscreen stack existed before)
- removeStacks(PINNED_STACK_ID);
- assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
- true /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
- }
-
- public void testRemovePipWithVisibleFullscreenStack() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a fullscreen activity, and a pip activity over that
- launchActivity(TEST_ACTIVITY);
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
- // top fullscreen activity
- removeStacks(PINNED_STACK_ID);
- assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
- false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
- }
-
- public void testRemovePipWithHiddenFullscreenStack() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
- // launch a pip activity over home
- launchActivity(TEST_ACTIVITY);
- launchHomeActivity();
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
- // stack, but that the home stack is still focused
- removeStacks(PINNED_STACK_ID);
- assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
- false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
- }
-
- public void testMovePipToBackWithNoFullscreenStack() throws Exception {
- if (!supportsPip()) return;
-
- // Start with a clean slate, remove all the stacks but home
- removeStacks(ALL_STACK_IDS_BUT_HOME);
-
- // Launch a pip activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Remove the stack and ensure that the task is now in the fullscreen stack (when no
- // fullscreen stack existed before)
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
- assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
- false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
- }
-
- public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a fullscreen activity, and a pip activity over that
- launchActivity(TEST_ACTIVITY);
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
- // top fullscreen activity
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
- assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
- false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
- }
-
- public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
- // launch a pip activity over home
- launchActivity(TEST_ACTIVITY);
- launchHomeActivity();
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
- // stack, but that the home stack is still focused
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
- assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY, HOME_STACK_ID,
- false /* expectTopTaskHasActivity */, true /* expectBottomTaskHasActivity */);
- }
-
- public void testPinnedStackAlwaysOnTop() throws Exception {
- if (!supportsPip()) return;
-
- // Launch activity into pinned stack and assert it's on top.
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
- assertPinnedStackIsOnTop();
-
- // Launch another activity in fullscreen stack and check that pinned stack is still on top.
- launchActivity(TEST_ACTIVITY);
- assertPinnedStackExists();
- assertPinnedStackIsOnTop();
-
- // Launch home and check that pinned stack is still on top.
- launchHomeActivity();
- assertPinnedStackExists();
- assertPinnedStackIsOnTop();
- }
-
- public void testAppOpsDenyPipOnPause() throws Exception {
- if (!supportsPip()) return;
-
- // Disable enter-pip and try to enter pip
- setAppOpsOpToMode(ActivityManagerTestBase.componentName,
- APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
-
- // Launch the PIP activity on pause
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackDoesNotExist();
-
- // Go home and ensure that there is no pinned stack
- launchHomeActivity();
- assertPinnedStackDoesNotExist();
-
- // Re-enable enter-pip-on-hide
- setAppOpsOpToMode(ActivityManagerTestBase.componentName,
- APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_ALLOWED);
- }
-
- public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
- if (!supportsPip()) return;
-
- // Try to enter picture-in-picture from an activity that has more than one activity in the
- // task and ensure that it works
- launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
- }
-
- public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
- if (!supportsPip()) return;
-
- /*
- * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
- * stopped and actually went into the pinned stack.
- *
- * Note that this is a workaround because to trigger the path that we want to happen in
- * activity manager, we need to add the leaving activity to the stopping state, which only
- * happens when a hidden stack is brought forward. Normally, this happens when you go home,
- * but since we can't launch into the home stack directly, we have a workaround.
- *
- * 1) Launch an activity in a new dynamic stack
- * 2) Resize the dynamic stack to non-fullscreen bounds
- * 3) Start the PiP activity that will enter picture-in-picture when paused in the
- * fullscreen stack
- * 4) Bring the activity in the dynamic stack forward to trigger PiP
- */
- int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
- resizeStack(stackId, 0, 0, 500, 500);
- // Launch an activity that will enter PiP when it is paused with a delay that is long enough
- // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
- // trigger the current system pause timeout (currently 500ms)
- launchActivityInStack(PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID,
- EXTRA_ENTER_PIP_ON_PAUSE, "true",
- EXTRA_ON_PAUSE_DELAY, "350",
- EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
- launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
- assertPinnedStackExists();
- }
-
- public void testDisallowEnterPipActivityLocked() throws Exception {
- if (!supportsPip()) return;
-
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
- ActivityTask task =
- mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
-
- // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
- // when paused
- executeShellCommand("am task lock " + task.mTaskId);
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackDoesNotExist();
- launchHomeActivity();
- assertPinnedStackDoesNotExist();
- executeShellCommand("am task lock stop");
- }
-
- public void testConfigurationChangeOrderDuringTransition() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a PiP activity and ensure configuration change only happened once, and that the
- // configuration change happened after the picture-in-picture and multi-window callbacks
- launchActivity(PIP_ACTIVITY);
- String logSeparator = clearLogcat();
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
- waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
- assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
-
- // Trigger it to go back to fullscreen and ensure that only triggered one configuration
- // change as well
- logSeparator = clearLogcat();
- launchActivity(PIP_ACTIVITY);
- waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
- assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
- }
-
- public void testEnterPipInterruptedCallbacks() throws Exception {
- if (!supportsPip()) return;
-
- // Slow down the transition animations for this test
- setWindowTransitionAnimationDurationScale(20);
-
- // Launch a PiP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- // Wait until the PiP activity has moved into the pinned stack (happens before the
- // transition has started)
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
-
- // Relaunch the PiP activity back into fullscreen
- String logSeparator = clearLogcat();
- launchActivity(PIP_ACTIVITY);
- // Wait until the PiP activity is reparented into the fullscreen stack (happens after the
- // transition has finished)
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-
- // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
- // configuration change (since none was sent)
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
- PIP_ACTIVITY, logSeparator);
- assertTrue(lifecycleCounts.mConfigurationChangedCount == 0);
- assertTrue(lifecycleCounts.mPictureInPictureModeChangedCount == 1);
- assertTrue(lifecycleCounts.mMultiWindowModeChangedCount == 1);
-
- // Reset the animation scale
- setWindowTransitionAnimationDurationScale(1);
- }
-
- public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a PiP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Dismiss it
- String logSeparator = clearLogcat();
- removeStacks(PINNED_STACK_ID);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
-
- // Confirm that we get stop before the multi-window and picture-in-picture mode change
- // callbacks
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
- logSeparator);
- if (lifecycleCounts.mStopCount != 1) {
- fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mStopCount
- + " onStop() calls, expecting 1");
- } else if (lifecycleCounts.mPictureInPictureModeChangedCount != 1) {
- fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mPictureInPictureModeChangedCount
- + " onPictureInPictureModeChanged() calls, expecting 1");
- } else if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
- fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mMultiWindowModeChangedCount
- + " onMultiWindowModeChanged() calls, expecting 1");
- } else {
- int lastStopLine = lifecycleCounts.mLastStopLineIndex;
- int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
- int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
- if (!(lastStopLine < lastPipLine && lastPipLine < lastMwLine)) {
- fail(PIP_ACTIVITY + " has received callbacks in unexpected order. Expected:"
- + " stop < pip < mw, but got line indices: " + lastStopLine + ", "
- + lastPipLine + ", " + lastMwLine + " respectively");
- }
- }
- }
-
- public void testPreventSetAspectRatioWhileExpanding() throws Exception {
- if (!supportsPip()) return;
-
- // Launch the PiP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-
- // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
- // call to set the aspect ratio did not prevent the PiP from returning to fullscreen
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_EXPAND_PIP
- + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR + " 123456789"
- + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR + " 100000000");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- assertPinnedStackDoesNotExist();
- }
-
- public void testSetRequestedOrientationWhilePinned() throws Exception {
- if (!supportsPip()) return;
-
- // Launch the PiP activity fixed as portrait, and enter picture-in-picture
- launchActivity(PIP_ACTIVITY,
- EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
- EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
-
- // Request that the orientation is set to landscape
- executeShellCommand("am broadcast -a "
- + PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION + " -e "
- + EXTRA_FIXED_ORIENTATION + " " + String.valueOf(ORIENTATION_LANDSCAPE));
-
- // Launch the activity back into fullscreen and ensure that it is now in landscape
- launchActivity(PIP_ACTIVITY);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- assertPinnedStackDoesNotExist();
- assertTrue(mAmWmState.getWmState().getLastOrientation() == ORIENTATION_LANDSCAPE);
- }
-
- public void testWindowButtonEntersPip() throws Exception {
- if (!supportsPip()) return;
-
- // Launch the PiP activity trigger the window button, ensure that we have entered PiP
- launchActivity(PIP_ACTIVITY);
- pressWindowButton();
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
- }
-
- public void testFinishPipActivityWithTaskOverlay() throws Exception {
- if (!supportsPip()) return;
-
- // Launch PiP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
- int taskId = mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getTopTask().mTaskId;
-
- // Ensure that we don't any any other overlays as a result of launching into PIP
- launchHomeActivity();
-
- // Launch task overlay activity into PiP activity task
- launchActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId, PINNED_STACK_ID);
-
- // Finish the PiP activity and ensure that there is no pinned stack
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_FINISH);
- mAmWmState.waitForWithAmState(mDevice, (amState) -> {
- ActivityStack stack = amState.getStackById(PINNED_STACK_ID);
- return stack == null;
- }, "Waiting for pinned stack to be removed...");
- assertPinnedStackDoesNotExist();
- }
-
- public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
- if (!supportsPip()) return;
-
- // Launch PiP activity
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- assertPinnedStackExists();
- int taskId = mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getTopTask().mTaskId;
-
- // Launch task overlay activity into PiP activity task
- launchActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId, PINNED_STACK_ID);
-
- // Finish the task overlay activity while animating and ensure that the PiP activity never
- // got resumed
- String logSeparator = clearLogcat();
- executeShellCommand("am stack resize-animated 4 20 20 500 500");
- executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
- mAmWmState.waitFor(mDevice, (amState, wmState) -> !amState.containsActivity(
- TRANSLUCENT_TEST_ACTIVITY), "Waiting for test activity to finish...");
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
- logSeparator);
- assertTrue(lifecycleCounts.mResumeCount == 0);
- assertTrue(lifecycleCounts.mPauseCount == 0);
- }
-
- public void testPinnedStackWithDockedStack() throws Exception {
- if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
-
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- launchActivityToSide(true, false, TEST_ACTIVITY);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
- // Launch the activities again to take focus and make sure nothing is hidden
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
- launchActivityToSide(true, false, TEST_ACTIVITY);
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, true);
-
- // Go to recents to make sure that fullscreen stack is invisible
- // Some devices do not support recents or implement it differently (instead of using a
- // separate stack id or as an activity), for those cases the visibility asserts will be
- // ignored
- pressAppSwitchButton();
- if (mAmWmState.waitForRecentsActivityVisible(mDevice)) {
- mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
- mAmWmState.assertVisibility(TEST_ACTIVITY, false);
- }
- }
-
- public void testLaunchTaskByComponentMatchMultipleTasks() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
- // affinity
- launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
- launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
- assertPinnedStackExists();
-
- // Launch the root activity again...
- int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivityName(
- TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
- launchHomeActivity();
- launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
-
- // ...and ensure that the root activity task is found and reused, and that the pinned stack
- // is unaffected
- assertPinnedStackExists();
- mAmWmState.assertFocusedActivity("Expected root activity focused",
- TEST_ACTIVITY_WITH_SAME_AFFINITY);
- assertTrue(rootActivityTaskId == mAmWmState.getAmState().getTaskByActivityName(
- TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
- }
-
- public void testLaunchTaskByAffinityMatchMultipleTasks() throws Exception {
- if (!supportsPip()) return;
-
- // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
- // affinity, and also launch another activity in the same task, while finishing itself. As
- // a result, the task will not have a component matching the same activity as what it was
- // started with
- launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
- EXTRA_START_ACTIVITY, getActivityComponentName(TEST_ACTIVITY),
- EXTRA_FINISH_SELF_ON_RESUME, "true");
- mAmWmState.waitForValidState(mDevice, TEST_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY_WITH_SAME_AFFINITY, PINNED_STACK_ID);
- assertPinnedStackExists();
-
- // Launch the root activity again...
- int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivityName(
- TEST_ACTIVITY).mTaskId;
- launchHomeActivity();
- launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
-
- // ...and ensure that even while matching purely by task affinity, the root activity task is
- // found and reused, and that the pinned stack is unaffected
- assertPinnedStackExists();
- mAmWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
- assertTrue(rootActivityTaskId == mAmWmState.getAmState().getTaskByActivityName(
- TEST_ACTIVITY).mTaskId);
- }
-
- public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
- if (!supportsPip()) return;
-
- // Launch an activity into the pinned stack with a fixed affinity
- launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
- EXTRA_ENTER_PIP, "true",
- EXTRA_START_ACTIVITY, getActivityComponentName(PIP_ACTIVITY),
- EXTRA_FINISH_SELF_ON_RESUME, "true");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- assertPinnedStackExists();
-
- // Launch the root activity again, of the matching task and ensure that we expand to
- // fullscreen
- int activityTaskId = mAmWmState.getAmState().getTaskByActivityName(
- PIP_ACTIVITY).mTaskId;
- launchHomeActivity();
- launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, FULLSCREEN_WORKSPACE_STACK_ID);
- assertPinnedStackDoesNotExist();
- assertTrue(activityTaskId == mAmWmState.getAmState().getTaskByActivityName(
- PIP_ACTIVITY).mTaskId);
- }
-
- /** Test that reported display size corresponds to fullscreen after exiting PiP. */
- public void testDisplayMetricsPinUnpin() throws Exception {
- if (!supportsPip()) return;
-
- String logSeparator = clearLogcat();
- launchActivity(TEST_ACTIVITY);
- final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
- final ReportedSizes initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY,
- logSeparator);
- final Rectangle initialAppBounds = readAppBounds(TEST_ACTIVITY, logSeparator);
- assertNotNull("Must report display dimensions", initialSizes);
- assertNotNull("Must report app bounds", initialAppBounds);
-
- logSeparator = clearLogcat();
- launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
- mAmWmState.waitForValidState(mDevice, PIP_ACTIVITY, PINNED_STACK_ID);
- final ReportedSizes pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
- logSeparator);
- final Rectangle pinnedAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
- assertFalse("Reported display size when pinned must be different from default",
- initialSizes.equals(pinnedSizes));
- assertFalse("Reported app bounds when pinned must be different from default",
- initialAppBounds.width == pinnedAppBounds.width
- && initialAppBounds.height == pinnedAppBounds.height);
-
- logSeparator = clearLogcat();
- launchActivityInStack(PIP_ACTIVITY, defaultDisplayStackId);
- final ReportedSizes finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
- logSeparator);
- final Rectangle finalAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
- assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
- assertEquals("Must report default app width after exiting PiP", initialAppBounds.width,
- finalAppBounds.width);
- assertEquals("Must report default app height after exiting PiP", initialAppBounds.height,
- finalAppBounds.height);
- }
-
- private static final Pattern sAppBoundsPattern = Pattern.compile(
- "(.+)appBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)(.*)");
-
- /** Read app bounds in last applied configuration from logs. */
- private Rectangle readAppBounds(String activityName, String logSeparator) throws Exception {
- final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- final Matcher matcher = sAppBoundsPattern.matcher(line);
- if (matcher.matches()) {
- final int left = Integer.parseInt(matcher.group(2));
- final int top = Integer.parseInt(matcher.group(3));
- final int right = Integer.parseInt(matcher.group(4));
- final int bottom = Integer.parseInt(matcher.group(5));
- return new Rectangle(left, top, right - left, bottom - top);
- }
- }
- return null;
- }
-
- /**
- * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
- * that the {@param focusedStackId} is focused, and checks the top and/or bottom tasks in the
- * fullscreen stack if {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity}
- * are set respectively.
- */
- private void assertPinnedStackStateOnMoveToFullscreen(String activityName, int focusedStackId,
- boolean expectTopTaskHasActivity, boolean expectBottomTaskHasActivity)
- throws Exception {
- mAmWmState.waitForFocusedStack(mDevice, focusedStackId);
- mAmWmState.assertFocusedStack("Wrong focused stack", focusedStackId);
- mAmWmState.waitForActivityState(mDevice, activityName, STATE_STOPPED);
- assertTrue(mAmWmState.getAmState().hasActivityState(activityName, STATE_STOPPED));
- assertPinnedStackDoesNotExist();
-
- if (expectTopTaskHasActivity) {
- ActivityTask topTask = mAmWmState.getAmState().getStackById(
- FULLSCREEN_WORKSPACE_STACK_ID).getTopTask();
- assertTrue(topTask.containsActivity(ActivityManagerTestBase.getActivityComponentName(
- activityName)));
- }
- if (expectBottomTaskHasActivity) {
- ActivityTask bottomTask = mAmWmState.getAmState().getStackById(
- FULLSCREEN_WORKSPACE_STACK_ID).getBottomTask();
- assertTrue(bottomTask.containsActivity(ActivityManagerTestBase.getActivityComponentName(
- activityName)));
- }
- }
-
- /**
- * Asserts that the pinned stack bounds does not intersect with the IME bounds.
- */
- private void assertPinnedStackDoesNotIntersectIME() throws Exception {
- // Ensure that the IME is visible
- WindowManagerState wmState = mAmWmState.getWmState();
- wmState.computeState(mDevice);
- WindowManagerState.WindowState imeWinState = wmState.getInputMethodWindowState();
- assertTrue(imeWinState != null);
-
- // Ensure that the PIP movement is constrained by the display bounds intersecting the
- // non-IME bounds
- Rectangle imeContentFrame = imeWinState.getContentFrame();
- Rectangle imeContentInsets = imeWinState.getGivenContentInsets();
- Rectangle imeBounds = new Rectangle(imeContentFrame.x + imeContentInsets.x,
- imeContentFrame.y + imeContentInsets.y,
- imeContentFrame.width - imeContentInsets.width,
- imeContentFrame.height - imeContentInsets.height);
- wmState.computeState(mDevice);
- Rectangle pipMovementBounds = wmState.getPinnedStackMomentBounds();
- assertTrue(!pipMovementBounds.intersects(imeBounds));
- }
-
- /**
- * Asserts that the pinned stack bounds is contained in the display bounds.
- */
- private void assertPinnedStackActivityIsInDisplayBounds(String activity) throws Exception {
- final WindowManagerState.WindowState windowState = getWindowState(activity);
- final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(
- windowState.getDisplayId());
- final Rectangle displayRect = display.getDisplayRect();
- final Rectangle pinnedStackBounds =
- mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
- assertTrue(displayRect.contains(pinnedStackBounds));
- }
-
- /**
- * Asserts that the pinned stack exists.
- */
- private void assertPinnedStackExists() throws Exception {
- mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
- }
-
- /**
- * Asserts that the pinned stack does not exist.
- */
- private void assertPinnedStackDoesNotExist() throws Exception {
- mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.", PINNED_STACK_ID);
- }
-
- /**
- * Asserts that the pinned stack is the front stack.
- */
- private void assertPinnedStackIsOnTop() throws Exception {
- mAmWmState.assertFrontStack("Pinned stack must always be on top.", PINNED_STACK_ID);
- }
-
- /**
- * Asserts that the activity received exactly one of each of the callbacks when entering and
- * exiting picture-in-picture.
- */
- private void assertValidPictureInPictureCallbackOrder(String activityName, String logSeparator)
- throws Exception {
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
- logSeparator);
-
- if (lifecycleCounts.mConfigurationChangedCount != 1) {
- fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
- + " onConfigurationChanged() calls, expecting 1");
- } else if (lifecycleCounts.mPictureInPictureModeChangedCount != 1) {
- fail(activityName + " has received " + lifecycleCounts.mPictureInPictureModeChangedCount
- + " onPictureInPictureModeChanged() calls, expecting 1");
- } else if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
- fail(activityName + " has received " + lifecycleCounts.mMultiWindowModeChangedCount
- + " onMultiWindowModeChanged() calls, expecting 1");
- } else {
- int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
- int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
- int lastConfigLine = lifecycleCounts.mLastConfigurationChangedLineIndex;
- if (!(lastPipLine < lastMwLine && lastMwLine < lastConfigLine)) {
- fail(activityName + " has received callbacks in unexpected order. Expected:"
- + " pip < mw < config change, but got line indices: " + lastPipLine + ", "
- + lastMwLine + ", " + lastConfigLine + " respectively");
- }
- }
- }
-
- /**
- * Waits until the expected picture-in-picture callbacks have been made.
- */
- private void waitForValidPictureInPictureCallbacks(String activityName, String logSeparator)
- throws Exception {
- mAmWmState.waitFor(mDevice, (amState, wmState) -> {
- try {
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
- activityName, logSeparator);
- return lifecycleCounts.mConfigurationChangedCount == 1 &&
- lifecycleCounts.mPictureInPictureModeChangedCount == 1 &&
- lifecycleCounts.mMultiWindowModeChangedCount == 1;
- } catch (Exception e) {
- return false;
- }
- }, "Waiting for picture-in-picture activity callbacks...");
- }
-
- /**
- * @return the window state for the given {@param activity}'s window.
- */
- private WindowManagerState.WindowState getWindowState(String activity) throws Exception {
- String windowName = getWindowName(activity);
- mAmWmState.computeState(mDevice, new String[] {activity});
- final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
- mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
- return tempWindowList.get(0);
- }
-
- /**
- * Compares two floats with a common epsilon.
- */
- private boolean floatEquals(float f1, float f2) {
- return Math.abs(f1 - f2) < FLOAT_COMPARE_EPSILON;
- }
-
- /**
- * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
- */
- private void tapToFinishPip() throws Exception {
- Rectangle pinnedStackBounds =
- mAmWmState.getAmState().getStackById(PINNED_STACK_ID).getBounds();
- int tapX = pinnedStackBounds.x + pinnedStackBounds.width - 100;
- int tapY = pinnedStackBounds.y + pinnedStackBounds.height - 100;
- executeShellCommand(String.format("input tap %d %d", tapX, tapY));
- }
-
- /**
- * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
- */
- private void launchActivityAsTaskOverlay(String activityName, int taskId, int stackId)
- throws Exception {
- executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
-
- mAmWmState.waitForValidState(mDevice, activityName, stackId);
- }
-
- /**
- * Sets an app-ops op for a given package to a given mode.
- */
- private void setAppOpsOpToMode(String packageName, String op, int mode) throws Exception {
- executeShellCommand(String.format("appops set %s %s %d", packageName, op, mode));
- }
-
- /**
- * Triggers the window keycode.
- */
- private void pressWindowButton() throws Exception {
- executeShellCommand(INPUT_KEYEVENT_WINDOW);
- }
-
- /**
- * TODO: Improve tests check to actually check that apps are not interactive instead of checking
- * if the stack is focused.
- */
- private void pinnedStackTester(String startActivityCmd, String topActivityName,
- boolean moveTopToPinnedStack, boolean isFocusable) throws Exception {
-
- executeShellCommand(startActivityCmd);
- if (moveTopToPinnedStack) {
- executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
- }
-
- mAmWmState.waitForValidState(mDevice, topActivityName, PINNED_STACK_ID);
- mAmWmState.computeState(mDevice, null);
-
- if (supportsPip()) {
- final String windowName = getWindowName(topActivityName);
- assertPinnedStackExists();
- mAmWmState.assertFrontStack("Pinned stack must be the front stack.", PINNED_STACK_ID);
- mAmWmState.assertVisibility(topActivityName, true);
-
- if (isFocusable) {
- mAmWmState.assertFocusedStack(
- "Pinned stack must be the focused stack.", PINNED_STACK_ID);
- mAmWmState.assertFocusedActivity(
- "Pinned activity must be focused activity.", topActivityName);
- mAmWmState.assertFocusedWindow(
- "Pinned window must be focused window.", windowName);
- // Not checking for resumed state here because PiP overlay can be launched on top
- // in different task by SystemUI.
- } else {
- // Don't assert that the stack is not focused as a focusable PiP overlay can be
- // launched on top as a task overlay by SystemUI.
- mAmWmState.assertNotFocusedActivity(
- "Pinned activity can't be the focused activity.", topActivityName);
- mAmWmState.assertNotResumedActivity(
- "Pinned activity can't be the resumed activity.", topActivityName);
- mAmWmState.assertNotFocusedWindow(
- "Pinned window can't be focused window.", windowName);
- }
- } else {
- mAmWmState.assertDoesNotContainStack(
- "Must not contain pinned stack.", PINNED_STACK_ID);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
deleted file mode 100644
index 3aa3ce0..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import java.lang.Exception;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import junit.framework.Assert;
-
-import static com.android.ddmlib.Log.LogLevel.INFO;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerReplaceWindowTests
- */
-public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
-
- private static final String SLOW_CREATE_ACTIVITY_NAME = "SlowCreateActivity";
- private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
-
- private List<String> mTempWindowTokens = new ArrayList();
-
- public void testReplaceWindow_Dock_Relaunch() throws Exception {
- testReplaceWindow_Dock(true);
- }
-
- public void testReplaceWindow_Dock_NoRelaunch() throws Exception {
- testReplaceWindow_Dock(false);
- }
-
- private void testReplaceWindow_Dock(boolean relaunch) throws Exception {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(INFO, "Skipping test: no multi-window support");
- return;
- }
-
- final String activityName =
- relaunch ? SLOW_CREATE_ACTIVITY_NAME : NO_RELAUNCH_ACTIVITY_NAME;
- final String windowName = getWindowName(activityName);
- final String amStartCmd = getAmStartCmd(activityName);
-
- executeShellCommand(amStartCmd);
-
- // Sleep 2 seconds, then check if the window is started properly.
- // SlowCreateActivity will do a sleep inside its onCreate() to simulate a
- // slow-starting app. So instead of relying on WindowManagerState's
- // retrying mechanism, we do an explicit sleep to avoid excess spews
- // from WindowManagerState.
- if (SLOW_CREATE_ACTIVITY_NAME.equals(activityName)) {
- Thread.sleep(2000);
- }
-
- CLog.logAndDisplay(INFO, "==========Before Docking========");
- final String oldToken = getWindowToken(windowName, activityName);
-
- // Move to docked stack
- final int taskId = getActivityTaskId(activityName);
- final String cmd = AM_MOVE_TASK + taskId + " " + DOCKED_STACK_ID + " true";
- executeShellCommand(cmd);
-
- // Sleep 5 seconds, then check if the window is replaced properly.
- Thread.sleep(5000);
-
- CLog.logAndDisplay(INFO, "==========After Docking========");
- final String newToken = getWindowToken(windowName, activityName);
-
- // For both relaunch and not relaunch case, we'd like the window to be kept.
- Assert.assertEquals("Window replaced while docking.", oldToken, newToken);
- }
-
- private String getWindowToken(String windowName, String activityName)
- throws Exception {
- mAmWmState.computeState(mDevice, new String[] {activityName});
-
- mAmWmState.assertVisibility(activityName, true);
-
- mAmWmState.getWmState().getMatchingWindowTokens(windowName, mTempWindowTokens);
-
- Assert.assertEquals("Should have exactly one window for the activity.",
- 1, mTempWindowTokens.size());
-
- return mTempWindowTokens.get(0);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
deleted file mode 100644
index 63aa1ab..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerTransitionSelectionTests.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-
-import static android.server.cts.WindowManagerState.TRANSIT_ACTIVITY_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_TASK_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_TASK_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_INTRA_CLOSE;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_INTRA_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
-
-/**
- * This test tests the transition type selection logic in ActivityManager/
- * WindowManager. BottomActivity is started first, then TopActivity, and we
- * check the transition type that the system selects when TopActivity enters
- * or exits under various setups.
- *
- * Note that we only require the correct transition type to be reported (eg.
- * TRANSIT_ACTIVITY_OPEN, TRANSIT_TASK_CLOSE, TRANSIT_WALLPAPER_OPEN, etc.).
- * The exact animation is unspecified and can be overridden.
- *
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerTransitionSelectionTests
- */
-@Presubmit
-public class ActivityManagerTransitionSelectionTests extends ActivityManagerTestBase {
-
- private static final String BOTTOM_ACTIVITY_NAME = "BottomActivity";
- private static final String TOP_ACTIVITY_NAME = "TopActivity";
- private static final String TRANSLUCENT_TOP_ACTIVITY_NAME = "TranslucentTopActivity";
-
- //------------------------------------------------------------------------//
-
- // Test activity open/close under normal timing
- public void testOpenActivity_NeitherWallpaper() throws Exception {
- testOpenActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_ACTIVITY_OPEN);
- }
-
- public void testCloseActivity_NeitherWallpaper() throws Exception {
- testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
- }
-
- public void testOpenActivity_BottomWallpaper() throws Exception {
- testOpenActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
- }
-
- public void testCloseActivity_BottomWallpaper() throws Exception {
- testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
- }
-
- public void testOpenActivity_BothWallpaper() throws Exception {
- testOpenActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
- }
-
- public void testCloseActivity_BothWallpaper() throws Exception {
- testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
- }
-
- //------------------------------------------------------------------------//
-
- // Test task open/close under normal timing
- public void testOpenTask_NeitherWallpaper() throws Exception {
- testOpenTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_TASK_OPEN);
- }
-
- public void testCloseTask_NeitherWallpaper() throws Exception {
- testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_TASK_CLOSE);
- }
-
- public void testOpenTask_BottomWallpaper() throws Exception {
- testOpenTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
- }
-
- public void testCloseTask_BottomWallpaper() throws Exception {
- testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
- }
-
- public void testOpenTask_BothWallpaper() throws Exception {
- testOpenTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
- }
-
- public void testCloseTask_BothWallpaper() throws Exception {
- testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
- false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
- }
-
- //------------------------------------------------------------------------//
-
- // Test activity close -- bottom activity slow in stopping
- // These simulate the case where the bottom activity is resumed
- // before AM receives its activitiyStopped
- public void testCloseActivity_NeitherWallpaper_SlowStop() throws Exception {
- testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
- true /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
- }
-
- public void testCloseActivity_BottomWallpaper_SlowStop() throws Exception {
- testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
- true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
- }
-
- public void testCloseActivity_BothWallpaper_SlowStop() throws Exception {
- testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
- true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
- }
-
- //------------------------------------------------------------------------//
-
- // Test task close -- bottom task top activity slow in stopping
- // These simulate the case where the bottom activity is resumed
- // before AM receives its activitiyStopped
- public void testCloseTask_NeitherWallpaper_SlowStop() throws Exception {
- testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
- true /*slowStop*/, TRANSIT_TASK_CLOSE);
- }
-
- public void testCloseTask_BottomWallpaper_SlowStop() throws Exception {
- testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
- true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
- }
-
- public void testCloseTask_BothWallpaper_SlowStop() throws Exception {
- testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
- true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
- }
-
- //------------------------------------------------------------------------//
-
- /// Test closing of translucent activity/task
- public void testCloseActivity_NeitherWallpaper_Translucent() throws Exception {
- testCloseActivityTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
- TRANSIT_ACTIVITY_CLOSE);
- }
-
- public void testCloseActivity_BottomWallpaper_Translucent() throws Exception {
- testCloseActivityTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
- TRANSIT_WALLPAPER_OPEN);
- }
-
- public void testCloseActivity_BothWallpaper_Translucent() throws Exception {
- testCloseActivityTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
- TRANSIT_WALLPAPER_INTRA_CLOSE);
- }
-
- public void testCloseTask_NeitherWallpaper_Translucent() throws Exception {
- testCloseTaskTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
- TRANSIT_TASK_CLOSE);
- }
-
- public void testCloseTask_BottomWallpaper_Translucent() throws Exception {
- testCloseTaskTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
- TRANSIT_WALLPAPER_OPEN);
- }
-
- public void testCloseTask_BothWallpaper_Translucent() throws Exception {
- testCloseTaskTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
- TRANSIT_WALLPAPER_INTRA_CLOSE);
- }
-
- //------------------------------------------------------------------------//
-
- private void testOpenActivity(boolean bottomWallpaper,
- boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
- testTransitionSelection(true /*testOpen*/, false /*testNewTask*/,
- bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
- }
- private void testCloseActivity(boolean bottomWallpaper,
- boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
- testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
- bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
- }
- private void testOpenTask(boolean bottomWallpaper,
- boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
- testTransitionSelection(true /*testOpen*/, true /*testNewTask*/,
- bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
- }
- private void testCloseTask(boolean bottomWallpaper,
- boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
- testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
- bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
- }
- private void testCloseActivityTranslucent(boolean bottomWallpaper,
- boolean topWallpaper, String expectedTransit) throws Exception {
- testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
- bottomWallpaper, topWallpaper, true /*topTranslucent*/,
- false /*slowStop*/, expectedTransit);
- }
- private void testCloseTaskTranslucent(boolean bottomWallpaper,
- boolean topWallpaper, String expectedTransit) throws Exception {
- testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
- bottomWallpaper, topWallpaper, true /*topTranslucent*/,
- false /*slowStop*/, expectedTransit);
- }
- //------------------------------------------------------------------------//
-
- private void testTransitionSelection(
- boolean testOpen, boolean testNewTask,
- boolean bottomWallpaper, boolean topWallpaper, boolean topTranslucent,
- boolean testSlowStop, String expectedTransit) throws Exception {
- String bottomStartCmd = getAmStartCmd(BOTTOM_ACTIVITY_NAME);
- if (bottomWallpaper) {
- bottomStartCmd += " --ez USE_WALLPAPER true";
- }
- if (testSlowStop) {
- bottomStartCmd += " --ei STOP_DELAY 3000";
- }
- executeShellCommand(bottomStartCmd);
-
- final String topActivityName = topTranslucent ?
- TRANSLUCENT_TOP_ACTIVITY_NAME : TOP_ACTIVITY_NAME;
- final String[] bottomActivityArray = new String[] {BOTTOM_ACTIVITY_NAME};
- final String[] topActivityArray = new String[] {topActivityName};
-
- mAmWmState.computeState(mDevice, bottomActivityArray);
-
- String topStartCmd = getAmStartCmd(topActivityName);
- if (testNewTask) {
- topStartCmd += " -f 0x18000000";
- }
- if (topWallpaper) {
- topStartCmd += " --ez USE_WALLPAPER true";
- }
- if (!testOpen) {
- topStartCmd += " --ei FINISH_DELAY 1000";
- }
- executeShellCommand(topStartCmd);
- Thread.sleep(5000);
- if (testOpen) {
- mAmWmState.computeState(mDevice, topActivityArray);
- } else {
- mAmWmState.computeState(mDevice, bottomActivityArray);
- }
-
- assertEquals("Picked wrong transition", expectedTransit,
- mAmWmState.getWmState().getLastTransition());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
deleted file mode 100644
index 8467af2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/AnimationBackgroundTests.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.AnimationBackgroundTests
- */
-public class AnimationBackgroundTests extends ActivityManagerTestBase {
-
- public void testAnimationBackground_duringAnimation() throws Exception {
- launchActivity(LAUNCHING_ACTIVITY);
- getLaunchActivityBuilder()
- .setTargetActivityName("AnimationTestActivity")
- .setWaitForLaunched(false)
- .execute();
-
- // Make sure we are in the middle of the animation.
- mAmWmState.waitForWithWmState(mDevice, state -> state
- .getStack(FULLSCREEN_WORKSPACE_STACK_ID)
- .isWindowAnimationBackgroundSurfaceShowing(),
- "***Waiting for animation background showing");
- assertTrue("window animation background needs to be showing", mAmWmState.getWmState()
- .getStack(FULLSCREEN_WORKSPACE_STACK_ID)
- .isWindowAnimationBackgroundSurfaceShowing());
- }
-
- public void testAnimationBackground_gone() throws Exception {
- launchActivity(LAUNCHING_ACTIVITY);
- getLaunchActivityBuilder().setTargetActivityName("AnimationTestActivity").execute();
- mAmWmState.computeState(mDevice, new String[] { "AnimationTestActivity "});
- assertFalse("window animation background needs to be gone", mAmWmState.getWmState()
- .getStack(FULLSCREEN_WORKSPACE_STACK_ID)
- .isWindowAnimationBackgroundSurfaceShowing());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
deleted file mode 100644
index 0ebbfe2..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/DisplaySizeTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-/**
- * Ensure that compatibility dialog is shown when launching an application with
- * an unsupported smallest width.
- *
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.DisplaySizeTest
- */
-public class DisplaySizeTest extends DeviceTestCase {
- private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
- private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
-
- private static final String AM_START_COMMAND = "am start -n %s/%s.%s";
- private static final String AM_FORCE_STOP = "am force-stop %s";
-
- private static final int ACTIVITY_TIMEOUT_MILLIS = 1000;
- private static final int WINDOW_TIMEOUT_MILLIS = 1000;
-
- private ITestDevice mDevice;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mDevice = getDevice();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- try {
- resetDensity();
-
- // Ensure app process is stopped.
- forceStopPackage("android.displaysize.app");
- forceStopPackage("android.server.cts");
- } catch (DeviceNotAvailableException e) {
- // Do nothing.
- }
- }
-
- public void testCompatibilityDialog() throws Exception {
- // Launch some other app (not to perform density change on launcher).
- startActivity("android.server.cts", "TestActivity");
- verifyWindowDisplayed("TestActivity", ACTIVITY_TIMEOUT_MILLIS);
-
- setUnsupportedDensity();
-
- // Launch target app.
- startActivity("android.displaysize.app", "SmallestWidthActivity");
- verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
- verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
- }
-
- public void testCompatibilityDialogWhenFocused() throws Exception {
- startActivity("android.displaysize.app", "SmallestWidthActivity");
- verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
-
- setUnsupportedDensity();
-
- verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
- }
-
- public void testCompatibilityDialogAfterReturn() throws Exception {
- // Launch target app.
- startActivity("android.displaysize.app", "SmallestWidthActivity");
- verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
- // Launch another activity.
- startOtherActivityOnTop("android.displaysize.app", "SmallestWidthActivity");
- verifyWindowDisplayed("TestActivity", ACTIVITY_TIMEOUT_MILLIS);
-
- setUnsupportedDensity();
-
- // Go back.
- mDevice.executeShellCommand("input keyevent 4");
-
- verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
- verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
- }
-
- private void setUnsupportedDensity() throws DeviceNotAvailableException {
- // Set device to 0.85 zoom. It doesn't matter that we're zooming out
- // since the feature verifies that we're in a non-default density.
- final int stableDensity = getStableDensity();
- final int targetDensity = (int) (stableDensity * 0.85);
- setDensity(targetDensity);
- }
-
- private int getStableDensity() {
- try {
- final String densityProp;
- if (mDevice.getSerialNumber().startsWith("emulator-")) {
- densityProp = DENSITY_PROP_EMULATOR;
- } else {
- densityProp = DENSITY_PROP_DEVICE;
- }
-
- return Integer.parseInt(mDevice.getProperty(densityProp));
- } catch (DeviceNotAvailableException e) {
- return 0;
- }
- }
-
- private void setDensity(int targetDensity) throws DeviceNotAvailableException {
- mDevice.executeShellCommand("wm density " + targetDensity);
-
- // Verify that the density is changed.
- final String output = mDevice.executeShellCommand("wm density");
- final boolean success = output.contains("Override density: " + targetDensity);
-
- assertTrue("Failed to set density to " + targetDensity, success);
- }
-
- private void resetDensity() throws DeviceNotAvailableException {
- mDevice.executeShellCommand("wm density reset");
- }
-
- private void forceStopPackage(String packageName) throws DeviceNotAvailableException {
- final String forceStopCmd = String.format(AM_FORCE_STOP, packageName);
- mDevice.executeShellCommand(forceStopCmd);
- }
-
- private void startActivity(String packageName, String activityName)
- throws DeviceNotAvailableException {
- mDevice.executeShellCommand(getStartCommand(packageName, activityName));
- }
-
- private void startOtherActivityOnTop(String packageName, String activityName)
- throws DeviceNotAvailableException {
- final String startCmd = getStartCommand(packageName, activityName)
- + " -f 0x20000000 --ez launch_another_activity true";
- mDevice.executeShellCommand(startCmd);
- }
-
- private String getStartCommand(String packageName, String activityName) {
- return String.format(AM_START_COMMAND, packageName, packageName, activityName);
- }
-
- private void verifyWindowDisplayed(String windowName, long timeoutMillis)
- throws DeviceNotAvailableException {
- boolean success = false;
-
- // Verify that compatibility dialog is shown within 1000ms.
- final long timeoutTimeMillis = System.currentTimeMillis() + timeoutMillis;
- while (!success && System.currentTimeMillis() < timeoutTimeMillis) {
- final String output = mDevice.executeShellCommand("dumpsys window");
- success = output.contains(windowName);
- }
-
- assertTrue(windowName + " was not displayed", success);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
deleted file mode 100644
index a441e56..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardLockedTests
- */
-public class KeyguardLockedTests extends KeyguardTestBase {
-
- private static final String SHOW_WHEN_LOCKED_ACTIVITY = "ShowWhenLockedActivity";
- private static final String PIP_ACTIVITY = "PipActivity";
- private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
- "android.server.cts.PipActivity.enter_pip";
- private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- setLockCredential();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- tearDownLockCredentials();
- }
-
- public void testLockAndUnlock() throws Exception {
- if (!isHandheld()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- unlockDeviceWithCredential();
- mAmWmState.waitForKeyguardGone(mDevice);
- assertKeyguardGone();
- }
-
- public void testDismissKeyguard() throws Exception {
- if (!isHandheld()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- launchActivity("DismissKeyguardActivity");
- enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone(mDevice);
- assertKeyguardGone();
- mAmWmState.assertVisibility("DismissKeyguardActivity", true);
- }
-
- public void testDismissKeyguard_whileOccluded() throws Exception {
- if (!isHandheld()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ACTIVITY });
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- launchActivity("DismissKeyguardActivity");
- enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone(mDevice);
- assertKeyguardGone();
- mAmWmState.assertVisibility("DismissKeyguardActivity", true);
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
- }
-
- public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
- if (!isHandheld()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ACTIVITY });
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
- enterAndConfirmLockCredential();
-
- // Make sure we stay on Keyguard.
- assertShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- }
-
- public void testDismissKeyguardActivity_method() throws Exception {
- if (!isHandheld()) {
- return;
- }
- final String logSeparator = clearLogcat();
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity("DismissKeyguardMethodActivity");
- enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone(mDevice);
- mAmWmState.computeState(mDevice, new String[] { "DismissKeyguardMethodActivity"});
- mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceededInLogcat(logSeparator);
- }
-
- public void testDismissKeyguardActivity_method_cancelled() throws Exception {
- if (!isHandheld()) {
- return;
- }
- final String logSeparator = clearLogcat();
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity("DismissKeyguardMethodActivity");
- pressBackButton();
- assertOnDismissCancelledInLogcat(logSeparator);
- mAmWmState.computeState(mDevice, new String[] {});
- mAmWmState.assertVisibility("DismissKeyguardMethodActivity", false);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- unlockDeviceWithCredential();
- }
-
- public void testEnterPipOverKeyguard() throws Exception {
- if (!isHandheld() || !supportsPip()) {
- return;
- }
-
- // Go to the keyguard
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
-
- // Enter PiP on an activity on top of the keyguard, and ensure that it prompts the user for
- // their credentials and does not enter picture-in-picture yet
- launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
- mAmWmState.waitForKeyguardShowingAndOccluded(mDevice);
- assertShowingAndOccluded();
- mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.", PINNED_STACK_ID);
-
- // Enter the credentials and ensure that the activity actually entered picture-in-picture
- enterAndConfirmLockCredential();
- mAmWmState.waitForKeyguardGone(mDevice);
- assertKeyguardGone();
- mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
- }
-
- public void testShowWhenLockedActivityAndPipActivity() throws Exception {
- if (!isHandheld() || !supportsPip()) {
- return;
- }
-
- launchActivity(PIP_ACTIVITY);
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
- mAmWmState.computeState(mDevice, new String[] { PIP_ACTIVITY });
- mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-
- launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
- mAmWmState.computeState(mDevice, new String[] { SHOW_WHEN_LOCKED_ACTIVITY });
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
-
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndOccluded(mDevice);
- assertShowingAndOccluded();
- mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
- mAmWmState.assertVisibility(PIP_ACTIVITY, false);
- }
-
- public void testShowWhenLockedPipActivity() throws Exception {
- if (!isHandheld() || !supportsPip()) {
- return;
- }
-
- launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
- executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
- mAmWmState.computeState(mDevice, new String[] { PIP_ACTIVITY });
- mAmWmState.assertContainsStack("Must contain pinned stack.", PINNED_STACK_ID);
- mAmWmState.assertVisibility(PIP_ACTIVITY, true);
-
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- mAmWmState.assertVisibility(PIP_ACTIVITY, false);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTestBase.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTestBase.java
deleted file mode 100644
index d578644..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTestBase.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.StateLogger.log;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class KeyguardTestBase extends ActivityManagerTestBase {
-
- protected void assertShowingAndOccluded() {
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardOccluded);
- }
-
- protected void assertShowingAndNotOccluded() {
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardOccluded);
- }
-
- protected void assertKeyguardGone() {
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- }
-
- protected void assertOnDismissSucceededInLogcat(String logSeparator) throws Exception {
- assertInLogcat("KeyguardDismissLoggerCallback", "onDismissSucceeded", logSeparator);
- }
-
- protected void assertOnDismissCancelledInLogcat(String logSeparator) throws Exception {
- assertInLogcat("KeyguardDismissLoggerCallback", "onDismissCancelled", logSeparator);
- }
-
- protected void assertOnDismissErrorInLogcat(String logSeparator) throws Exception {
- assertInLogcat("KeyguardDismissLoggerCallback", "onDismissError", logSeparator);
- }
-
- private void assertInLogcat(String activityName, String entry, String logSeparator)
- throws Exception {
- final Pattern pattern = Pattern.compile("(.+)" + entry);
- int tries = 0;
- while (tries < 5) {
- final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
- log("Looking at logcat");
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- log(line);
- Matcher matcher = pattern.matcher(line);
- if (matcher.matches()) {
- return;
- }
- }
- tries++;
- Thread.sleep(500);
- }
- fail("Not in logcat: " + entry);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
deleted file mode 100644
index 8c6f037..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTests.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.server.cts.WindowManagerState.WindowState;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTests
- */
-public class KeyguardTests extends KeyguardTestBase {
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- // Set screen lock (swipe)
- setLockDisabled(false);
- }
-
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
-
- tearDownLockCredentials();
- }
-
- public void testKeyguardHidesActivity() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("TestActivity");
- mAmWmState.computeState(mDevice, new String[] { "TestActivity"});
- mAmWmState.assertVisibility("TestActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- assertShowingAndNotOccluded();
- mAmWmState.assertVisibility("TestActivity", false);
- unlockDevice();
- }
-
- public void testShowWhenLockedActivity() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("ShowWhenLockedActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity"});
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- assertShowingAndOccluded();
- pressHomeButton();
- unlockDevice();
- }
-
- /**
- * Tests whether dialogs from SHOW_WHEN_LOCKED activities are also visible if Keyguard is
- * showing.
- */
- public void testShowWhenLockedActivity_withDialog() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("ShowWhenLockedWithDialogActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedWithDialogActivity"});
- mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
- assertTrue(mAmWmState.getWmState().allWindowsVisible(
- getWindowName("ShowWhenLockedWithDialogActivity")));
- assertShowingAndOccluded();
- pressHomeButton();
- unlockDevice();
- }
-
- /**
- * Tests whether multiple SHOW_WHEN_LOCKED activities are shown if the topmost is translucent.
- */
- public void testMultipleShowWhenLockedActivities() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("ShowWhenLockedActivity");
- launchActivity("ShowWhenLockedTranslucentActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity",
- "ShowWhenLockedTranslucentActivity"});
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
- assertShowingAndOccluded();
- pressHomeButton();
- unlockDevice();
- }
-
- /**
- * If we have a translucent SHOW_WHEN_LOCKED_ACTIVITY, the wallpaper should also be showing.
- */
- public void testTranslucentShowWhenLockedActivity() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("ShowWhenLockedTranslucentActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedTranslucentActivity"});
- mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
- assertWallpaperShowing();
- assertShowingAndOccluded();
- pressHomeButton();
- unlockDevice();
- }
-
- /**
- * If we have a translucent SHOW_WHEN_LOCKED activity, the activity behind should not be shown.
- */
- public void testTranslucentDoesntRevealBehind() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("TestActivity");
- launchActivity("ShowWhenLockedTranslucentActivity");
- mAmWmState.computeState(mDevice, new String[] { "TestActivity",
- "ShowWhenLockedTranslucentActivity"});
- mAmWmState.assertVisibility("TestActivity", true);
- mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
- mAmWmState.assertVisibility("TestActivity", false);
- assertShowingAndOccluded();
- pressHomeButton();
- unlockDevice();
- }
-
- public void testDialogShowWhenLockedActivity() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("ShowWhenLockedDialogActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedDialogActivity"});
- mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
- assertWallpaperShowing();
- assertShowingAndOccluded();
- pressHomeButton();
- unlockDevice();
- }
-
- /**
- * Test that showWhenLocked activity is fullscreen when shown over keyguard
- */
- public void testShowWhenLockedActivityWhileSplit() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset() || !supportsSplitScreenMultiWindow()) {
- return;
- }
- launchActivityInDockStack(LAUNCHING_ACTIVITY);
- launchActivityToSide(true, false, "ShowWhenLockedActivity");
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- gotoKeyguard();
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity" });
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- assertShowingAndOccluded();
- mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
- ActivityManagerTestBase.DOCKED_STACK_ID);
- pressHomeButton();
- unlockDevice();
- }
-
- /**
- * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
- */
- public void testDismissKeyguardActivity() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity("DismissKeyguardActivity");
- mAmWmState.waitForKeyguardShowingAndOccluded(mDevice);
- mAmWmState.computeState(mDevice, new String[] { "DismissKeyguardActivity"});
- mAmWmState.assertVisibility("DismissKeyguardActivity", true);
- assertShowingAndOccluded();
- }
-
- public void testDismissKeyguardActivity_method() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- final String logSeparator = clearLogcat();
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity("DismissKeyguardMethodActivity");
- mAmWmState.waitForKeyguardGone(mDevice);
- mAmWmState.computeState(mDevice, new String[] { "DismissKeyguardMethodActivity"});
- mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceededInLogcat(logSeparator);
- }
-
- public void testDismissKeyguardActivity_method_notTop() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- final String logSeparator = clearLogcat();
- gotoKeyguard();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity("BroadcastReceiverActivity");
- launchActivity("TestActivity");
- executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguardMethod true");
- assertOnDismissErrorInLogcat(logSeparator);
- }
-
- public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- final String logSeparator = clearLogcat();
- sleepDevice();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity("TurnScreenOnDismissKeyguardActivity");
- mAmWmState.waitForKeyguardGone(mDevice);
- mAmWmState.computeState(mDevice, new String[] { "TurnScreenOnDismissKeyguardActivity"});
- mAmWmState.assertVisibility("TurnScreenOnDismissKeyguardActivity", true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceededInLogcat(logSeparator);
- }
-
- public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- launchActivity("ShowWhenLockedActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity" });
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- assertShowingAndOccluded();
- executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
- assertShowingAndOccluded();
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- }
-
- public void testKeyguardLock() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- launchActivity("KeyguardLockActivity");
- mAmWmState.computeState(mDevice, new String[] { "KeyguardLockActivity" });
- mAmWmState.assertVisibility("KeyguardLockActivity", true);
- executeShellCommand(FINISH_ACTIVITY_BROADCAST);
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- }
-
- public void testUnoccludeRotationChange() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- executeShellCommand(getAmStartCmd("ShowWhenLockedActivity"));
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity" });
- mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
- setDeviceRotation(1);
- pressHomeButton();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- mAmWmState.waitForDisplayUnfrozen(mDevice);
- mAmWmState.assertSanity();
- mAmWmState.assertHomeActivityVisible(false);
- assertShowingAndNotOccluded();
- mAmWmState.assertVisibility("ShowWhenLockedActivity", false);
- }
-
- private void assertWallpaperShowing() {
- WindowState wallpaper =
- mAmWmState.getWmState().findFirstWindowWithType(WindowState.TYPE_WALLPAPER);
- assertNotNull(wallpaper);
- assertTrue(wallpaper.isShown());
- }
-
- public void testDismissKeyguardAttrActivity_method_turnScreenOn() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
- sleepDevice();
-
- final String logSeparator = clearLogcat();
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(activityName);
- mAmWmState.waitForKeyguardGone(mDevice);
- mAmWmState.assertVisibility(activityName, true);
- assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertOnDismissSucceededInLogcat(logSeparator);
- assertTrue(isDisplayOn());
- }
-
- public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
-
- setLockCredential();
- sleepDevice();
-
- mAmWmState.computeState(mDevice, null);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- launchActivity(activityName);
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- mAmWmState.assertVisibility(activityName, false);
- assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
- assertTrue(isDisplayOn());
- }
-
- public void testScreenOffWhileOccludedStopsActivity() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- final String logSeparator = clearLogcat();
- gotoKeyguard();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- assertShowingAndNotOccluded();
- launchActivity("ShowWhenLockedAttrActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedAttrActivity" });
- mAmWmState.assertVisibility("ShowWhenLockedAttrActivity", true);
- assertShowingAndOccluded();
- sleepDevice();
- assertSingleLaunchAndStop("ShowWhenLockedAttrActivity", logSeparator);
- }
-
- public void testScreenOffCausesSingleStop() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- final String logSeparator = clearLogcat();
- launchActivity("TestActivity");
- mAmWmState.assertVisibility("TestActivity", true);
- sleepDevice();
- assertSingleLaunchAndStop("TestActivity", logSeparator);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
deleted file mode 100644
index 5971ccb..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardTransitionTests.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.server.cts.WindowManagerState.TRANSIT_KEYGUARD_UNOCCLUDE;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.KeyguardTransitionTests
- */
-public class KeyguardTransitionTests extends ActivityManagerTestBase {
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- // Set screen lock (swipe)
- setLockDisabled(false);
- }
-
- public void testUnlock() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("TestActivity");
- gotoKeyguard();
- unlockDevice();
- mAmWmState.computeState(mDevice, new String[] { "TestActivity"} );
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
- mAmWmState.getWmState().getLastTransition());
- }
-
- public void testUnlockWallpaper() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("WallpaperActivity");
- gotoKeyguard();
- unlockDevice();
- mAmWmState.computeState(mDevice, new String[] { "WallpaperActivity"} );
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
- mAmWmState.getWmState().getLastTransition());
- }
-
- public void testOcclude() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- gotoKeyguard();
- launchActivity("ShowWhenLockedActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedActivity"} );
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
- mAmWmState.getWmState().getLastTransition());
- }
-
- public void testUnocclude() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- gotoKeyguard();
- launchActivity("ShowWhenLockedActivity");
- launchActivity("TestActivity");
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- mAmWmState.computeState(mDevice, null);
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
- mAmWmState.getWmState().getLastTransition());
- }
-
- public void testNewActivityDuringOccluded() throws Exception {
- if (!isHandheld() || isUiModeLockedToVrHeadset()) {
- return;
- }
- launchActivity("ShowWhenLockedActivity");
- gotoKeyguard();
- launchActivity("ShowWhenLockedWithDialogActivity");
- mAmWmState.computeState(mDevice, new String[] { "ShowWhenLockedWithDialogActivity" });
- assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
- mAmWmState.getWmState().getLastTransition());
- }
-
- public void testOccludeManifestAttr() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- String activityName = "ShowWhenLockedAttrActivity";
-
- gotoKeyguard();
- final String logSeparator = clearLogcat();
- launchActivity(activityName);
- mAmWmState.computeState(mDevice, new String[] {activityName});
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
- mAmWmState.getWmState().getLastTransition());
- assertSingleLaunch(activityName, logSeparator);
- }
-
- public void testOccludeAttrRemove() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- String activityName = "ShowWhenLockedAttrRemoveAttrActivity";
-
- gotoKeyguard();
- String logSeparator = clearLogcat();
- launchActivity(activityName);
- mAmWmState.computeState(mDevice, new String[] {activityName});
- assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
- mAmWmState.getWmState().getLastTransition());
- assertSingleLaunch(activityName, logSeparator);
-
- gotoKeyguard();
- logSeparator = clearLogcat();
- launchActivity(activityName);
- mAmWmState.computeState(mDevice, new String[] {activityName});
- assertSingleStartAndStop(activityName, logSeparator);
- }
-
- public void testNewActivityDuringOccludedWithAttr() throws Exception {
- if (!isHandheld()) {
- return;
- }
-
- String activityName1 = "ShowWhenLockedAttrActivity";
- String activityName2 = "ShowWhenLockedAttrWithDialogActivity";
-
- launchActivity(activityName1);
- gotoKeyguard();
- launchActivity(activityName2);
- mAmWmState.computeState(mDevice, new String[] { activityName2 });
- assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
- mAmWmState.getWmState().getLastTransition());
- }
-
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/SplashscreenTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/SplashscreenTests.java
deleted file mode 100644
index ed7f383..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/SplashscreenTests.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import java.awt.Color;
-import java.awt.Rectangle;
-import java.awt.image.BufferedImage;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.SplashscreenTests
- */
-public class SplashscreenTests extends ActivityManagerTestBase {
-
- public void testSplashscreenContent() throws Exception {
- launchActivityNoWait("SplashscreenActivity");
- mAmWmState.waitForAppTransitionIdle(mDevice);
- mAmWmState.getWmState().getStableBounds();
- final BufferedImage image = takeScreenshot();
-
- // Use ratios to flexibly accomodate circular or not quite rectangular displays
- // Note: Color.BLACK is the pixel color outside of the display region
- assertColors(image, mAmWmState.getWmState().getStableBounds(),
- Color.RED.getRGB(), 0.50f, Color.BLACK.getRGB(), 0.01f);
- }
-
- private void assertColors(BufferedImage img, Rectangle bounds, int primaryColor,
- float expectedPrimaryRatio, int secondaryColor, float acceptableWrongRatio) {
-
- int primaryPixels = 0;
- int secondaryPixels = 0;
- int wrongPixels = 0;
- for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
- for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
- assertTrue(x < img.getWidth());
- assertTrue(y < img.getHeight());
- final int color = img.getRGB(x, y);
- if (primaryColor == color) {
- primaryPixels++;
- } else if (secondaryColor == color) {
- secondaryPixels++;
- } else {
- wrongPixels++;
- }
- }
- }
-
- final int totalPixels = bounds.width * bounds.height;
- final float primaryRatio = (float) primaryPixels / totalPixels;
- if (primaryRatio < expectedPrimaryRatio) {
- fail("Less than " + (expectedPrimaryRatio * 100.0f)
- + "% of pixels have non-primary color primaryPixels=" + primaryPixels
- + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
- }
-
- // Some pixels might be covered by screen shape decorations, like rounded corners.
- // On circular displays, there is an antialiased edge.
- final float wrongRatio = (float) wrongPixels / totalPixels;
- if (wrongRatio > acceptableWrongRatio) {
- fail("More than " + (acceptableWrongRatio * 100.0f)
- + "% of pixels have wrong color primaryPixels=" + primaryPixels
- + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml
deleted file mode 100755
index ee3bff8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.server.translucentapp">
- <application android:label="CtsTranslucentApp">
- <activity android:name=".TranslucentLandscapeActivity"
- android:theme="@android:style/Theme.Translucent.NoTitleBar"
- android:exported="true"
- android:screenOrientation="landscape">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java
deleted file mode 100644
index 471e3b6..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.translucentapp;
-
-import android.app.Activity;
-
-public class TranslucentLandscapeActivity extends Activity {
-}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/AndroidManifest.xml
deleted file mode 100755
index 43c85f5..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.server.translucentapp26">
- <application android:label="CtsTranslucentApp26">
- <activity android:name="android.server.translucentapp.TranslucentLandscapeActivity"
- android:theme="@android:style/Theme.Translucent.NoTitleBar"
- android:exported="true"
- android:screenOrientation="landscape">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/AndroidManifest.xml
deleted file mode 100644
index d3ae55e..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.server.displayservice">
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <application android:label="CtsDisplayService">
- <service android:name=".VirtualDisplayService"
- android:exported="true" />
- </application>
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/src/android/server/displayservice/VirtualDisplayService.java b/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/src/android/server/displayservice/VirtualDisplayService.java
deleted file mode 100644
index eb58963..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/src/android/server/displayservice/VirtualDisplayService.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.displayservice;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.Surface;
-
-public class VirtualDisplayService extends Service {
- private static final String NOTIFICATION_CHANNEL_ID = "cts/VirtualDisplayService";
- private static final String TAG = "VirtualDisplayService";
-
- private static final int FOREGROUND_ID = 1;
-
- private static final int DENSITY = 160;
- private static final int HEIGHT = 480;
- private static final int WIDTH = 800;
-
- private ImageReader mReader;
- private VirtualDisplay mVirtualDisplay;
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(new NotificationChannel(
- NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
- NotificationManager.IMPORTANCE_DEFAULT));
- Notification notif = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(android.R.drawable.ic_dialog_alert)
- .build();
- startForeground(FOREGROUND_ID, notif);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- String command = intent.getStringExtra("command");
- Log.d(TAG, "Got command: " + command);
-
- if ("create".equals(command)) {
- createVirtualDisplay(intent);
- } if ("off".equals(command)) {
- mVirtualDisplay.setSurface(null);
- } else if ("on".equals(command)) {
- mVirtualDisplay.setSurface(mReader.getSurface());
- } else if ("destroy".equals(command)) {
- destroyVirtualDisplay();
- stopSelf();
- }
-
- return START_NOT_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- private void createVirtualDisplay(Intent intent) {
- mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
-
- final DisplayManager displayManager = getSystemService(DisplayManager.class);
- final String name = "CtsVirtualDisplay";
-
- int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
- if (intent.getBooleanExtra("show_content_when_locked", false /* defaultValue */)) {
- flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
- }
- mVirtualDisplay = displayManager.createVirtualDisplay(
- name, WIDTH, HEIGHT, DENSITY, mReader.getSurface(), flags);
- }
-
- private void destroyVirtualDisplay() {
- mVirtualDisplay.release();
- mReader.close();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/Android.mk b/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/Android.mk
deleted file mode 100644
index 0df59e7..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
-
-LOCAL_MODULE := cts-display-service-app-util
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/src/android/server/displayservice/DisplayHelper.java b/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/src/android/server/displayservice/DisplayHelper.java
deleted file mode 100644
index 89683a0..0000000
--- a/hostsidetests/services/activityandwindowmanager/displayserviceapp/util/src/android/server/displayservice/DisplayHelper.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package android.server.displayservice;
-
-import static junit.framework.Assert.assertTrue;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-public class DisplayHelper {
- private static final String VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay";
- private static final String VIRTUAL_DISPLAY_SERVICE =
- "android.server.displayservice/.VirtualDisplayService";
- private static final Pattern mDisplayDevicePattern = Pattern.compile(
- ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
-
- private boolean mCreated;
- private final ITestDevice mDevice;
-
- public DisplayHelper(ITestDevice device) {
- mDevice = device;
- }
-
- public void createAndWaitForDisplay(boolean external, boolean requestShowWhenLocked)
- throws DeviceNotAvailableException {
- StringBuilder command =
- new StringBuilder("am startfgservice -n " + VIRTUAL_DISPLAY_SERVICE);
- command.append(" --es command create");
- if (external) {
- command.append(" --ez external_display true");
- }
- if (requestShowWhenLocked) {
- command.append(" --ez show_content_when_locked true");
- }
- mDevice.executeShellCommand(command.toString());
-
- waitForDisplayState(mDevice, false /* default */, true /* exists */, true /* on */);
- mCreated = true;
- }
-
- public void turnDisplayOff() throws DeviceNotAvailableException {
- mDevice.executeShellCommand(
- "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command off");
- waitForDisplayState(mDevice, false /* default */, true /* exists */, false /* on */);
- }
-
- public void turnDisplayOn() throws DeviceNotAvailableException {
- mDevice.executeShellCommand(
- "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command on");
- waitForDisplayState(mDevice, false /* default */, true /* exists */, true /* on */);
- }
-
- public void releaseDisplay() throws DeviceNotAvailableException {
- if (mCreated) {
- mDevice.executeShellCommand(
- "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command destroy");
- waitForDisplayState(mDevice, false /* default */, false /* exists */, true /* on */);
- }
- mCreated = false;
- }
-
- public static void waitForDefaultDisplayState(ITestDevice device, boolean wantOn)
- throws DeviceNotAvailableException {
- waitForDisplayState(device, true /* default */, true /* exists */, wantOn);
- }
-
- public static boolean getDefaultDisplayState(ITestDevice device)
- throws DeviceNotAvailableException {
- return getDisplayState(device, true);
- }
-
- private static void waitForDisplayState(
- ITestDevice device, boolean defaultDisplay, boolean wantExists, boolean wantOn)
- throws DeviceNotAvailableException {
- int tries = 0;
- boolean done = false;
- do {
- if (tries > 0) {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- // Oh well
- }
- }
-
- Boolean state = getDisplayState(device, defaultDisplay);
- done = (!wantExists && state == null)
- || (wantExists && state != null && state == wantOn);
-
- tries++;
- } while (tries < 10 && !done);
-
- assertTrue(done);
- }
-
- private static Boolean getDisplayState(ITestDevice device, boolean defaultDisplay)
- throws DeviceNotAvailableException {
- final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- device.executeShellCommand("dumpsys display", outputReceiver);
- String dump = outputReceiver.getOutput();
-
- boolean displayExists = false;
- boolean displayOn = false;
- for (String line : dump.split("\\n")) {
- Matcher matcher = mDisplayDevicePattern.matcher(line);
- if (matcher.matches()) {
- if ((defaultDisplay && line.contains("FLAG_DEFAULT_DISPLAY"))
- || (!defaultDisplay && VIRTUAL_DISPLAY_NAME.equals(matcher.group(1)))) {
- return "ON".equals(matcher.group(2));
- }
- }
- }
- return null;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/Android.mk b/hostsidetests/services/activityandwindowmanager/util/Android.mk
deleted file mode 100644
index 993ba94..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed
-
-LOCAL_MODULE := cts-amwm-util
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
deleted file mode 100644
index a54ed19..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityAndWindowManagersState.java
+++ /dev/null
@@ -1,844 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityManagerState.RESIZE_MODE_RESIZEABLE;
-import static android.server.cts.ActivityManagerTestBase.DOCKED_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.PINNED_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.componentName;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-import android.server.cts.ActivityManagerState.ActivityTask;
-import android.server.cts.WindowManagerState.Display;
-import android.server.cts.WindowManagerState.WindowStack;
-import android.server.cts.WindowManagerState.WindowState;
-import android.server.cts.WindowManagerState.WindowTask;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import junit.framework.Assert;
-
-import java.awt.Rectangle;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.BiPredicate;
-import java.util.function.BooleanSupplier;
-import java.util.function.Predicate;
-
-/** Combined state of the activity manager and window manager. */
-public class ActivityAndWindowManagersState extends Assert {
-
- // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM)
- // (Needed in host-side tests to convert dp to px.)
- private static final int DISPLAY_DENSITY_DEFAULT = 160;
- public static final int DEFAULT_DISPLAY_ID = 0;
-
- // Default minimal size of resizable task, used if none is set explicitly.
- // Must be kept in sync with 'default_minimal_size_resizable_task' dimen from frameworks/base.
- private static final int DEFAULT_RESIZABLE_TASK_SIZE_DP = 220;
-
- // Default minimal size of a resizable PiP task, used if none is set explicitly.
- // Must be kept in sync with 'default_minimal_size_pip_resizable_task' dimen from
- // frameworks/base.
- private static final int DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP = 108;
-
- private ActivityManagerState mAmState = new ActivityManagerState();
- private WindowManagerState mWmState = new WindowManagerState();
-
- private final List<WindowManagerState.WindowState> mTempWindowList = new ArrayList<>();
-
- private boolean mUseActivityNames = true;
-
- /**
- * Compute AM and WM state of device, check sanity and bounds.
- * WM state will include only visible windows, stack and task bounds will be compared.
- *
- * @param device test device.
- * @param waitForActivitiesVisible array of activity names to wait for.
- */
- public void computeState(ITestDevice device, String[] waitForActivitiesVisible)
- throws Exception {
- computeState(device, waitForActivitiesVisible, true);
- }
-
- /**
- * Compute AM and WM state of device, check sanity and bounds.
- *
- * @param device test device.
- * @param waitForActivitiesVisible array of activity names to wait for.
- * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared,
- * 'false' otherwise.
- */
- void computeState(ITestDevice device, String[] waitForActivitiesVisible,
- boolean compareTaskAndStackBounds) throws Exception {
- waitForValidState(device, waitForActivitiesVisible, null /* stackIds */,
- compareTaskAndStackBounds);
-
- assertSanity();
- assertValidBounds(compareTaskAndStackBounds);
- }
-
- /**
- * By default computeState allows you to pass only the activity name it and
- * it will generate the full window name for the main activity window. In the
- * case of secondary application windows though this isn't helpful, as they
- * may follow a different format, so this method lets you disable that behavior,
- * prior to calling a computeState variant
- */
- void setUseActivityNamesForWindowNames(boolean useActivityNames) {
- mUseActivityNames = useActivityNames;
- }
-
- /**
- * Compute AM and WM state of device, wait for the activity records to be added, and
- * wait for debugger window to show up.
- *
- * This should only be used when starting with -D (debugger) option, where we pop up the
- * waiting-for-debugger window, but real activity window won't show up since we're waiting
- * for debugger.
- */
- void waitForDebuggerWindowVisible(
- ITestDevice device, String[] waitForActivityRecords) throws Exception {
- int retriesLeft = 5;
- do {
- mAmState.computeState(device);
- mWmState.computeState(device);
- if (shouldWaitForDebuggerWindow() ||
- shouldWaitForActivityRecords(waitForActivityRecords)) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
- }
-
- /**
- * Wait for the activity to appear and for valid state in AM and WM.
- *
- * @param device test device.
- * @param waitForActivityVisible name of activity to wait for.
- */
- void waitForValidState(ITestDevice device, String waitForActivityVisible)
- throws Exception {
- waitForValidState(device, new String[]{waitForActivityVisible}, null /* stackIds */,
- false /* compareTaskAndStackBounds */);
- }
-
- /**
- * Wait for the activity to appear in proper stack and for valid state in AM and WM.
- *
- * @param device test device.
- * @param waitForActivityVisible name of activity to wait for.
- * @param stackId id of the stack where provided activity should be found.
- */
- void waitForValidState(ITestDevice device, String waitForActivityVisible, int stackId)
- throws Exception {
- waitForValidState(device, new String[]{waitForActivityVisible}, new int[]{stackId},
- false /* compareTaskAndStackBounds */);
- }
-
- /**
- * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
- *
- * @param device test device.
- * @param waitForActivitiesVisible array of activity names to wait for.
- * @param stackIds ids of stack where provided activities should be found.
- * Pass null to skip this check.
- * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
- * for equality.
- */
- void waitForValidState(ITestDevice device, String[] waitForActivitiesVisible, int[] stackIds,
- boolean compareTaskAndStackBounds) throws Exception {
- waitForValidState(device, waitForActivitiesVisible, stackIds, compareTaskAndStackBounds,
- componentName);
- }
-
- /**
- * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
- *
- * @param device test device.
- * @param waitForActivitiesVisible array of activity names to wait for.
- * @param stackIds ids of stack where provided activities should be found.
- * Pass null to skip this check.
- * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
- * for equality.
- * @param packageName name of the package of activities that we're waiting for.
- */
- void waitForValidState(ITestDevice device, String[] waitForActivitiesVisible, int[] stackIds,
- boolean compareTaskAndStackBounds, String packageName) throws Exception {
- int retriesLeft = 5;
- do {
- // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
- // requesting dump in some intermediate state.
- mAmState.computeState(device);
- mWmState.computeState(device);
- if (shouldWaitForValidStacks(compareTaskAndStackBounds)
- || shouldWaitForActivities(waitForActivitiesVisible, stackIds, packageName)
- || shouldWaitForWindows()) {
- log("***Waiting for valid stacks and activities states...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
- }
-
- void waitForAllStoppedActivities(ITestDevice device) throws Exception {
- int retriesLeft = 5;
- do {
- mAmState.computeState(device);
- if (mAmState.containsStartedActivities()){
- log("***Waiting for valid stacks and activities states...");
- try {
- Thread.sleep(1500);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertFalse(mAmState.containsStartedActivities());
- }
-
- void waitForHomeActivityVisible(ITestDevice device) throws Exception {
- waitForValidState(device, mAmState.getHomeActivityName());
- }
-
- /** @return true if recents activity is visible. Devices without recents will return false */
- boolean waitForRecentsActivityVisible(ITestDevice device) throws Exception {
- waitForWithAmState(device, ActivityManagerState::isRecentsActivityVisible,
- "***Waiting for recents activity to be visible...");
- return mAmState.isRecentsActivityVisible();
- }
-
- void waitForKeyguardShowingAndNotOccluded(ITestDevice device) throws Exception {
- waitForWithAmState(device, state -> state.getKeyguardControllerState().keyguardShowing
- && !state.getKeyguardControllerState().keyguardOccluded,
- "***Waiting for Keyguard showing...");
- }
-
- void waitForKeyguardShowingAndOccluded(ITestDevice device) throws Exception {
- waitForWithAmState(device, state -> state.getKeyguardControllerState().keyguardShowing
- && state.getKeyguardControllerState().keyguardOccluded,
- "***Waiting for Keyguard showing and occluded...");
- }
-
- void waitForKeyguardGone(ITestDevice device) throws Exception {
- waitForWithAmState(device, state -> !state.getKeyguardControllerState().keyguardShowing,
- "***Waiting for Keyguard gone...");
- }
-
- void waitForRotation(ITestDevice device, int rotation) throws Exception {
- waitForWithWmState(device, state -> state.getRotation() == rotation,
- "***Waiting for Rotation: " + rotation);
- }
-
- void waitForDisplayUnfrozen(ITestDevice device) throws Exception {
- waitForWithWmState(device, state -> !state.isDisplayFrozen(),
- "***Waiting for Display unfrozen");
- }
-
- void waitForActivityState(ITestDevice device, String activityName, String... activityStates)
- throws Exception {
- waitForWithAmState(device, state -> {
- for (String activityState : activityStates) {
- if (state.hasActivityState(activityName, activityState)) {
- return true;
- }
- }
- return false;
- },
- "***Waiting for Activity State: " + activityStates);
- }
-
- void waitForFocusedStack(ITestDevice device, int stackId) throws Exception {
- waitForWithAmState(device, state -> state.getFocusedStackId() == stackId,
- "***Waiting for focused stack...");
- }
-
- void waitForAppTransitionIdle(ITestDevice device) throws Exception {
- waitForWithWmState(device,
- state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
- "***Waiting for app transition idle...");
- }
-
- void waitForWithAmState(ITestDevice device, Predicate<ActivityManagerState> waitCondition,
- String message) throws Exception{
- waitFor(device, (amState, wmState) -> waitCondition.test(amState), message);
- }
-
- void waitForWithWmState(ITestDevice device, Predicate<WindowManagerState> waitCondition,
- String message) throws Exception{
- waitFor(device, (amState, wmState) -> waitCondition.test(wmState), message);
- }
-
- void waitFor(ITestDevice device,
- BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message)
- throws Exception {
- waitFor(message, () -> {
- try {
- mAmState.computeState(device);
- mWmState.computeState(device);
- } catch (Exception e) {
- logE(e.toString());
- return false;
- }
- return waitCondition.test(mAmState, mWmState);
- });
- }
-
- void waitFor(String message, BooleanSupplier waitCondition) throws Exception {
- int retriesLeft = 5;
- do {
- if (!waitCondition.getAsBoolean()) {
- log(message);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
- }
-
- /** @return true if should wait for valid stacks state. */
- private boolean shouldWaitForValidStacks(boolean compareTaskAndStackBounds) {
- if (!taskListsInAmAndWmAreEqual()) {
- // We want to wait for equal task lists in AM and WM in case we caught them in the
- // middle of some state change operations.
- log("***taskListsInAmAndWmAreEqual=false");
- return true;
- }
- if (!stackBoundsInAMAndWMAreEqual()) {
- // We want to wait a little for the stacks in AM and WM to have equal bounds as there
- // might be a transition animation ongoing when we got the states from WM AM separately.
- log("***stackBoundsInAMAndWMAreEqual=false");
- return true;
- }
- try {
- // Temporary fix to avoid catching intermediate state with different task bounds in AM
- // and WM.
- assertValidBounds(compareTaskAndStackBounds);
- } catch (AssertionError e) {
- log("***taskBoundsInAMAndWMAreEqual=false : " + e.getMessage());
- return true;
- }
- final int stackCount = mAmState.getStackCount();
- if (stackCount == 0) {
- log("***stackCount=" + stackCount);
- return true;
- }
- final int resumedActivitiesCount = mAmState.getResumedActivitiesCount();
- if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount != 1) {
- log("***resumedActivitiesCount=" + resumedActivitiesCount);
- return true;
- }
- if (mAmState.getFocusedActivity() == null) {
- log("***focusedActivity=null");
- return true;
- }
- return false;
- }
-
- /** @return true if should wait for some activities to become visible. */
- private boolean shouldWaitForActivities(String[] waitForActivitiesVisible, int[] stackIds,
- String packageName) {
- if (waitForActivitiesVisible == null || waitForActivitiesVisible.length == 0) {
- return false;
- }
- // If the caller is interested in us waiting for some particular activity windows to be
- // visible before compute the state. Check for the visibility of those activity windows
- // and for placing them in correct stacks (if requested).
- boolean allActivityWindowsVisible = true;
- boolean tasksInCorrectStacks = true;
- List<WindowManagerState.WindowState> matchingWindowStates = new ArrayList<>();
- for (int i = 0; i < waitForActivitiesVisible.length; i++) {
- // Check if window is visible - it should be represented as one of the window states.
- final String windowName = mUseActivityNames ?
- ActivityManagerTestBase.getWindowName(packageName, waitForActivitiesVisible[i])
- : waitForActivitiesVisible[i];
- final String activityComponentName =
- ActivityManagerTestBase.getActivityComponentName(packageName,
- waitForActivitiesVisible[i]);
-
- mWmState.getMatchingVisibleWindowState(windowName, matchingWindowStates);
- boolean activityWindowVisible = !matchingWindowStates.isEmpty();
- if (!activityWindowVisible) {
- log("Activity window not visible: " + windowName);
- allActivityWindowsVisible = false;
- } else if (!mAmState.isActivityVisible(activityComponentName)) {
- log("Activity not visible: " + activityComponentName);
- allActivityWindowsVisible = false;
- } else if (stackIds != null) {
- // Check if window is already in stack requested by test.
- boolean windowInCorrectStack = false;
- for (WindowManagerState.WindowState ws : matchingWindowStates) {
- if (ws.getStackId() == stackIds[i]) {
- windowInCorrectStack = true;
- break;
- }
- }
- if (!windowInCorrectStack) {
- log("Window in incorrect stack: " + waitForActivitiesVisible[i]);
- tasksInCorrectStacks = false;
- }
- }
- }
- return !allActivityWindowsVisible || !tasksInCorrectStacks;
- }
-
- /** @return true if should wait valid windows state. */
- private boolean shouldWaitForWindows() {
- if (mWmState.getFrontWindow() == null) {
- log("***frontWindow=null");
- return true;
- }
- if (mWmState.getFocusedWindow() == null) {
- log("***focusedWindow=null");
- return true;
- }
- if (mWmState.getFocusedApp() == null) {
- log("***focusedApp=null");
- return true;
- }
-
- return false;
- }
-
- private boolean shouldWaitForDebuggerWindow() {
- List<WindowManagerState.WindowState> matchingWindowStates = new ArrayList<>();
- mWmState.getMatchingVisibleWindowState("android.server.cts", matchingWindowStates);
- for (WindowState ws : matchingWindowStates) {
- if (ws.isDebuggerWindow()) {
- return false;
- }
- }
- log("Debugger window not available yet");
- return true;
- }
-
- private boolean shouldWaitForActivityRecords(String[] waitForActivityRecords) {
- if (waitForActivityRecords == null || waitForActivityRecords.length == 0) {
- return false;
- }
- // Check if the activity records we're looking for is already added.
- for (int i = 0; i < waitForActivityRecords.length; i++) {
- if (!mAmState.isActivityVisible(waitForActivityRecords[i])) {
- log("ActivityRecord " + waitForActivityRecords[i] + " not visible yet");
- return true;
- }
- }
- return false;
- }
-
- ActivityManagerState getAmState() {
- return mAmState;
- }
-
- public WindowManagerState getWmState() {
- return mWmState;
- }
-
- void assertSanity() throws Exception {
- assertTrue("Must have stacks", mAmState.getStackCount() > 0);
- if (!mAmState.getKeyguardControllerState().keyguardShowing) {
- assertEquals("There should be one and only one resumed activity in the system.",
- 1, mAmState.getResumedActivitiesCount());
- }
- assertNotNull("Must have focus activity.", mAmState.getFocusedActivity());
-
- for (ActivityStack aStack : mAmState.getStacks()) {
- final int stackId = aStack.mStackId;
- for (ActivityTask aTask : aStack.getTasks()) {
- assertEquals("Stack can only contain its own tasks", stackId, aTask.mStackId);
- }
- }
-
- assertNotNull("Must have front window.", mWmState.getFrontWindow());
- assertNotNull("Must have focused window.", mWmState.getFocusedWindow());
- assertNotNull("Must have app.", mWmState.getFocusedApp());
- }
-
- void assertContainsStack(String msg, int stackId) throws Exception {
- assertTrue(msg, mAmState.containsStack(stackId));
- assertTrue(msg, mWmState.containsStack(stackId));
- }
-
- void assertDoesNotContainStack(String msg, int stackId) throws Exception {
- assertFalse(msg, mAmState.containsStack(stackId));
- assertFalse(msg, mWmState.containsStack(stackId));
- }
-
- void assertFrontStack(String msg, int stackId) throws Exception {
- assertEquals(msg, stackId, mAmState.getFrontStackId(DEFAULT_DISPLAY_ID));
- assertEquals(msg, stackId, mWmState.getFrontStackId(DEFAULT_DISPLAY_ID));
- }
-
- void assertFocusedStack(String msg, int stackId) throws Exception {
- assertEquals(msg, stackId, mAmState.getFocusedStackId());
- }
-
- void assertNotFocusedStack(String msg, int stackId) throws Exception {
- if (stackId == mAmState.getFocusedStackId()) {
- failNotEquals(msg, stackId, mAmState.getFocusedStackId());
- }
- }
-
- void assertFocusedActivity(String msg, String activityName) throws Exception {
- assertFocusedActivity(msg, componentName, activityName);
- }
-
- void assertFocusedActivity(String msg, String packageName, String activityName)
- throws Exception {
- final String componentName = ActivityManagerTestBase.getActivityComponentName(packageName,
- activityName);
- assertEquals(msg, componentName, mAmState.getFocusedActivity());
- assertEquals(msg, componentName, mWmState.getFocusedApp());
- }
-
- void assertNotFocusedActivity(String msg, String activityName) throws Exception {
- final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
- if (mAmState.getFocusedActivity().equals(componentName)) {
- failNotEquals(msg, mAmState.getFocusedActivity(), componentName);
- }
- if (mWmState.getFocusedApp().equals(componentName)) {
- failNotEquals(msg, mWmState.getFocusedApp(), componentName);
- }
- }
-
- void assertResumedActivity(String msg, String activityName) throws Exception {
- final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
- assertEquals(msg, componentName, mAmState.getResumedActivity());
- }
-
- void assertNotResumedActivity(String msg, String activityName) throws Exception {
- final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
- if (mAmState.getResumedActivity().equals(componentName)) {
- failNotEquals(msg, mAmState.getResumedActivity(), componentName);
- }
- }
-
- void assertFocusedWindow(String msg, String windowName) {
- assertEquals(msg, windowName, mWmState.getFocusedWindow());
- }
-
- void assertNotFocusedWindow(String msg, String windowName) {
- if (mWmState.getFocusedWindow().equals(windowName)) {
- failNotEquals(msg, mWmState.getFocusedWindow(), windowName);
- }
- }
-
- void assertFrontWindow(String msg, String windowName) {
- assertEquals(msg, windowName, mWmState.getFrontWindow());
- }
-
- void assertVisibility(String activityName, boolean visible) {
- final String activityComponentName =
- ActivityManagerTestBase.getActivityComponentName(activityName);
- final String windowName =
- ActivityManagerTestBase.getWindowName(activityName);
- assertVisibility(activityComponentName, windowName, visible);
- }
-
- private void assertVisibility(String activityComponentName, String windowName,
- boolean visible) {
- final boolean activityVisible = mAmState.isActivityVisible(activityComponentName);
- final boolean windowVisible = mWmState.isWindowVisible(windowName);
-
- if (visible) {
- assertTrue("Activity=" + activityComponentName + " must be visible.", activityVisible);
- assertTrue("Window=" + windowName + " must be visible.", windowVisible);
- } else {
- assertFalse("Activity=" + activityComponentName + " must NOT be visible.",
- activityVisible);
- assertFalse("Window=" + windowName + " must NOT be visible.", windowVisible);
- }
- }
-
- void assertHomeActivityVisible(boolean visible) {
- String name = mAmState.getHomeActivityName();
- assertNotNull(name);
- assertVisibility(name, getWindowNameForActivityName(name), visible);
- }
-
- /**
- * Asserts that the device default display minimim width is larger than the minimum task width.
- */
- void assertDeviceDefaultDisplaySize(ITestDevice device, String errorMessage) throws Exception {
- computeState(device, null);
- final int minTaskSizePx = defaultMinimalTaskSize(DEFAULT_DISPLAY_ID);
- final Display display = getWmState().getDisplay(DEFAULT_DISPLAY_ID);
- final Rectangle displayRect = display.getDisplayRect();
- if (Math.min(displayRect.width, displayRect.height) < minTaskSizePx) {
- fail(errorMessage);
- }
- }
-
- private String getWindowNameForActivityName(String activityName) {
- return activityName.replaceAll("(.*)\\/\\.", "$1/$1.");
- }
-
- boolean taskListsInAmAndWmAreEqual() {
- for (ActivityStack aStack : mAmState.getStacks()) {
- final int stackId = aStack.mStackId;
- final WindowStack wStack = mWmState.getStack(stackId);
- if (wStack == null) {
- log("Waiting for stack setup in WM, stackId=" + stackId);
- return false;
- }
-
- for (ActivityTask aTask : aStack.getTasks()) {
- if (wStack.getTask(aTask.mTaskId) == null) {
- log("Task is in AM but not in WM, waiting for it to settle, taskId="
- + aTask.mTaskId);
- return false;
- }
- }
-
- for (WindowTask wTask : wStack.mTasks) {
- if (aStack.getTask(wTask.mTaskId) == null) {
- log("Task is in WM but not in AM, waiting for it to settle, taskId="
- + wTask.mTaskId);
- return false;
- }
- }
- }
- return true;
- }
-
- int getStackPosition(int stackId) {
- int wmStackIndex = mWmState.getStackPosition(stackId);
- int amStackIndex = mAmState.getStackPosition(stackId);
- assertEquals("Window and activity manager must have the same stack position index",
- amStackIndex, wmStackIndex);
- return wmStackIndex;
- }
-
- boolean stackBoundsInAMAndWMAreEqual() {
- for (ActivityStack aStack : mAmState.getStacks()) {
- final int stackId = aStack.mStackId;
- final WindowStack wStack = mWmState.getStack(stackId);
- if (aStack.isFullscreen() != wStack.isFullscreen()) {
- log("Waiting for correct fullscreen state, stackId=" + stackId);
- return false;
- }
-
- final Rectangle aStackBounds = aStack.getBounds();
- final Rectangle wStackBounds = wStack.getBounds();
-
- if (aStack.isFullscreen()) {
- if (aStackBounds != null) {
- log("Waiting for correct stack state in AM, stackId=" + stackId);
- return false;
- }
- } else if (!Objects.equals(aStackBounds, wStackBounds)) {
- // If stack is not fullscreen - comparing bounds. Not doing it always because
- // for fullscreen stack bounds in WM can be either null or equal to display size.
- log("Waiting for stack bound equality in AM and WM, stackId=" + stackId);
- return false;
- }
- }
-
- return true;
- }
-
- /** Check task bounds when docked to top/left. */
- void assertDockedTaskBounds(int taskWidth, int taskHeight, String activityName) {
- // Task size can be affected by default minimal size.
- int defaultMinimalTaskSize = defaultMinimalTaskSize(
- mAmState.getStackById(ActivityManagerTestBase.DOCKED_STACK_ID).mDisplayId);
- int targetWidth = Math.max(taskWidth, defaultMinimalTaskSize);
- int targetHeight = Math.max(taskHeight, defaultMinimalTaskSize);
-
- assertEquals(new Rectangle(0, 0, targetWidth, targetHeight),
- mAmState.getTaskByActivityName(activityName).getBounds());
- }
-
- void assertValidBounds(boolean compareTaskAndStackBounds) {
- // Cycle through the stacks and tasks to figure out if the home stack is resizable
- final ActivityTask homeTask = mAmState.getHomeTask();
- final boolean homeStackIsResizable = homeTask != null
- && homeTask.getResizeMode().equals(RESIZE_MODE_RESIZEABLE);
-
- for (ActivityStack aStack : mAmState.getStacks()) {
- final int stackId = aStack.mStackId;
- final WindowStack wStack = mWmState.getStack(stackId);
- assertNotNull("stackId=" + stackId + " in AM but not in WM?", wStack);
-
- assertEquals("Stack fullscreen state in AM and WM must be equal stackId=" + stackId,
- aStack.isFullscreen(), wStack.isFullscreen());
-
- final Rectangle aStackBounds = aStack.getBounds();
- final Rectangle wStackBounds = wStack.getBounds();
-
- if (aStack.isFullscreen()) {
- assertNull("Stack bounds in AM must be null stackId=" + stackId, aStackBounds);
- } else {
- assertEquals("Stack bounds in AM and WM must be equal stackId=" + stackId,
- aStackBounds, wStackBounds);
- }
-
- for (ActivityTask aTask : aStack.getTasks()) {
- final int taskId = aTask.mTaskId;
- final WindowTask wTask = wStack.getTask(taskId);
- assertNotNull(
- "taskId=" + taskId + " in AM but not in WM? stackId=" + stackId, wTask);
-
- final boolean aTaskIsFullscreen = aTask.isFullscreen();
- final boolean wTaskIsFullscreen = wTask.isFullscreen();
- assertEquals("Task fullscreen state in AM and WM must be equal taskId=" + taskId
- + ", stackId=" + stackId, aTaskIsFullscreen, wTaskIsFullscreen);
-
- final Rectangle aTaskBounds = aTask.getBounds();
- final Rectangle wTaskBounds = wTask.getBounds();
- final Rectangle displayRect = mWmState.getDisplay(aStack.mDisplayId)
- .getDisplayRect();
-
- if (aTaskIsFullscreen) {
- assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId,
- aTaskBounds);
- } else if (!homeStackIsResizable && mWmState.isDockedStackMinimized()
- && displayRect.getWidth() > displayRect.getHeight()) {
- // When minimized using non-resizable launcher in landscape mode, it will move
- // the task offscreen in the negative x direction unlike portrait that crops.
- // The x value in the task bounds will not match the stack bounds since the
- // only the task was moved.
- assertEquals("Task bounds in AM and WM must match width taskId=" + taskId
- + ", stackId" + stackId, aTaskBounds.getWidth(),
- wTaskBounds.getWidth());
- assertEquals("Task bounds in AM and WM must match height taskId=" + taskId
- + ", stackId" + stackId, aTaskBounds.getHeight(),
- wTaskBounds.getHeight());
- assertEquals("Task bounds must match stack bounds y taskId=" + taskId
- + ", stackId" + stackId, aTaskBounds.getY(),
- wTaskBounds.getY());
- assertEquals("Task and stack bounds must match width taskId=" + taskId
- + ", stackId" + stackId, aStackBounds.getWidth(),
- wTaskBounds.getWidth());
- assertEquals("Task and stack bounds must match height taskId=" + taskId
- + ", stackId" + stackId, aStackBounds.getHeight(),
- wTaskBounds.getHeight());
- assertEquals("Task and stack bounds must match y taskId=" + taskId
- + ", stackId" + stackId, aStackBounds.getY(),
- wTaskBounds.getY());
- } else {
- assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId
- + ", stackId=" + stackId, aTaskBounds, wTaskBounds);
-
- if (compareTaskAndStackBounds && stackId != FREEFORM_WORKSPACE_STACK_ID) {
- int aTaskMinWidth = aTask.getMinWidth();
- int aTaskMinHeight = aTask.getMinHeight();
-
- if (aTaskMinWidth == -1 || aTaskMinHeight == -1) {
- // Minimal dimension(s) not set for task - it should be using defaults.
- int defaultMinimalSize = (stackId == PINNED_STACK_ID)
- ? defaultMinimalPinnedTaskSize(aStack.mDisplayId)
- : defaultMinimalTaskSize(aStack.mDisplayId);
-
- if (aTaskMinWidth == -1) {
- aTaskMinWidth = defaultMinimalSize;
- }
- if (aTaskMinHeight == -1) {
- aTaskMinHeight = defaultMinimalSize;
- }
- }
-
- if (aStackBounds.getWidth() >= aTaskMinWidth
- && aStackBounds.getHeight() >= aTaskMinHeight
- || stackId == PINNED_STACK_ID) {
- // Bounds are not smaller then minimal possible, so stack and task
- // bounds must be equal.
- assertEquals("Task bounds must be equal to stack bounds taskId="
- + taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds);
- } else if (stackId == DOCKED_STACK_ID && homeStackIsResizable
- && mWmState.isDockedStackMinimized()) {
- // Portrait if the display height is larger than the width
- if (displayRect.getHeight() > displayRect.getWidth()) {
- assertEquals("Task width must be equal to stack width taskId="
- + taskId + ", stackId=" + stackId,
- aStackBounds.getWidth(), wTaskBounds.getWidth());
- assertTrue("Task height must be greater than stack height "
- + "taskId=" + taskId + ", stackId=" + stackId,
- aStackBounds.getHeight() < wTaskBounds.getHeight());
- assertEquals("Task and stack x position must be equal taskId="
- + taskId + ", stackId=" + stackId,
- wTaskBounds.getX(), wStackBounds.getX());
- } else {
- assertTrue("Task width must be greater than stack width taskId="
- + taskId + ", stackId=" + stackId,
- aStackBounds.getWidth() < wTaskBounds.getWidth());
- assertEquals("Task height must be equal to stack height taskId="
- + taskId + ", stackId=" + stackId,
- aStackBounds.getHeight(), wTaskBounds.getHeight());
- assertEquals("Task and stack y position must be equal taskId="
- + taskId + ", stackId=" + stackId, wTaskBounds.getY(),
- wStackBounds.getY());
- }
- } else {
- // Minimal dimensions affect task size, so bounds of task and stack must
- // be different - will compare dimensions instead.
- int targetWidth = (int) Math.max(aTaskMinWidth,
- aStackBounds.getWidth());
- assertEquals("Task width must be set according to minimal width"
- + " taskId=" + taskId + ", stackId=" + stackId,
- targetWidth, (int) wTaskBounds.getWidth());
- int targetHeight = (int) Math.max(aTaskMinHeight,
- aStackBounds.getHeight());
- assertEquals("Task height must be set according to minimal height"
- + " taskId=" + taskId + ", stackId=" + stackId,
- targetHeight, (int) wTaskBounds.getHeight());
- }
- }
- }
- }
- }
- }
-
- static int dpToPx(float dp, int densityDpi){
- return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
- }
-
- private int defaultMinimalTaskSize(int displayId) {
- return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
- }
-
- private int defaultMinimalPinnedTaskSize(int displayId) {
- return dpToPx(DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
deleted file mode 100644
index fa79c5e..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerState.java
+++ /dev/null
@@ -1,910 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import java.awt.Rectangle;
-import java.lang.Integer;
-import java.lang.String;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
-import static android.server.cts.ActivityManagerTestBase.RECENTS_STACK_ID;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-class ActivityManagerState {
- public static final int DUMP_MODE_ACTIVITIES = 0;
-
- public static final String STATE_RESUMED = "RESUMED";
- public static final String STATE_PAUSED = "PAUSED";
- public static final String STATE_STOPPED = "STOPPED";
- public static final String STATE_DESTROYED = "DESTROYED";
-
- public static final String RESIZE_MODE_RESIZEABLE = "RESIZE_MODE_RESIZEABLE";
-
- private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity activities";
-
- // Copied from ActivityRecord.java
- private static final int APPLICATION_ACTIVITY_TYPE = 0;
- private static final int HOME_ACTIVITY_TYPE = 1;
- private static final int RECENTS_ACTIVITY_TYPE = 2;
-
- private final Pattern mDisplayIdPattern = Pattern.compile("Display #(\\d+).*");
- private final Pattern mStackIdPattern = Pattern.compile("Stack #(\\d+)\\:");
- private final Pattern mResumedActivityPattern =
- Pattern.compile("ResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}");
- private final Pattern mFocusedStackPattern =
- Pattern.compile("mFocusedStack=ActivityStack\\{(.+) stackId=(\\d+), (.+)\\}(.+)");
-
- private final Pattern[] mExtractStackExitPatterns =
- { mStackIdPattern, mResumedActivityPattern, mFocusedStackPattern, mDisplayIdPattern };
-
- // Stacks in z-order with the top most at the front of the list, starting with primary display.
- private final List<ActivityStack> mStacks = new ArrayList();
- // Stacks on all attached displays, in z-order with the top most at the front of the list.
- private final Map<Integer, List<ActivityStack>> mDisplayStacks = new HashMap<>();
- private KeyguardControllerState mKeyguardControllerState;
- private int mFocusedStackId = -1;
- private String mResumedActivityRecord = null;
- private final List<String> mResumedActivities = new ArrayList();
- private final LinkedList<String> mSysDump = new LinkedList();
-
- void computeState(ITestDevice device) throws DeviceNotAvailableException {
- computeState(device, DUMP_MODE_ACTIVITIES);
- }
-
- void computeState(ITestDevice device, int dumpMode) throws DeviceNotAvailableException {
- // It is possible the system is in the middle of transition to the right state when we get
- // the dump. We try a few times to get the information we need before giving up.
- int retriesLeft = 3;
- boolean retry = false;
- String dump = null;
-
- log("==============================");
- log(" ActivityManagerState ");
- log("==============================");
-
- do {
- if (retry) {
- log("***Incomplete AM state. Retrying...");
- // Wait half a second between retries for activity manager to finish transitioning.
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- }
-
- final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- String dumpsysCmd = "";
- switch (dumpMode) {
- case DUMP_MODE_ACTIVITIES:
- dumpsysCmd = DUMPSYS_ACTIVITY_ACTIVITIES; break;
- }
- device.executeShellCommand(dumpsysCmd, outputReceiver);
- dump = outputReceiver.getOutput();
- parseSysDump(dump);
-
- retry = mStacks.isEmpty() || mFocusedStackId == -1 || (mResumedActivityRecord == null
- || mResumedActivities.isEmpty()) && !mKeyguardControllerState.keyguardShowing;
- } while (retry && retriesLeft-- > 0);
-
- if (retry) {
- log(dump);
- }
-
- if (mStacks.isEmpty()) {
- logE("No stacks found...");
- }
- if (mFocusedStackId == -1) {
- logE("No focused stack found...");
- }
- if (mResumedActivityRecord == null) {
- logE("No focused activity found...");
- }
- if (mResumedActivities.isEmpty()) {
- logE("No resumed activities found...");
- }
- }
-
- private void parseSysDump(String sysDump) {
- reset();
-
- Collections.addAll(mSysDump, sysDump.split("\\n"));
-
- int currentDisplayId = 0;
- while (!mSysDump.isEmpty()) {
- final ActivityStack stack = ActivityStack.create(mSysDump, mStackIdPattern,
- mExtractStackExitPatterns, currentDisplayId);
-
- if (stack != null) {
- mStacks.add(stack);
- mDisplayStacks.get(currentDisplayId).add(stack);
- if (stack.mResumedActivity != null) {
- mResumedActivities.add(stack.mResumedActivity);
- }
- continue;
- }
-
- KeyguardControllerState controller = KeyguardControllerState.create(
- mSysDump, new Pattern[0]);
- if (controller != null) {
- mKeyguardControllerState = controller;
- continue;
- }
-
- final String line = mSysDump.pop().trim();
-
- Matcher matcher = mFocusedStackPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String stackId = matcher.group(2);
- log(stackId);
- mFocusedStackId = Integer.parseInt(stackId);
- continue;
- }
-
- matcher = mResumedActivityPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mResumedActivityRecord = matcher.group(3);
- log(mResumedActivityRecord);
- continue;
- }
-
- matcher = mDisplayIdPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String displayId = matcher.group(1);
- log(displayId);
- currentDisplayId = Integer.parseInt(displayId);
- mDisplayStacks.put(currentDisplayId, new ArrayList<>());
- }
- }
- }
-
- private void reset() {
- mStacks.clear();
- mFocusedStackId = -1;
- mResumedActivityRecord = null;
- mResumedActivities.clear();
- mSysDump.clear();
- mKeyguardControllerState = null;
- }
-
- int getFrontStackId(int displayId) {
- return mDisplayStacks.get(displayId).get(0).mStackId;
- }
-
- int getFocusedStackId() {
- return mFocusedStackId;
- }
-
- String getFocusedActivity() {
- return mResumedActivityRecord;
- }
-
- String getResumedActivity() {
- return mResumedActivities.get(0);
- }
-
- int getResumedActivitiesCount() {
- return mResumedActivities.size();
- }
-
- public KeyguardControllerState getKeyguardControllerState() {
- return mKeyguardControllerState;
- }
-
- boolean containsStack(int stackId) {
- return getStackById(stackId) != null;
- }
-
- ActivityStack getStackById(int stackId) {
- for (ActivityStack stack : mStacks) {
- if (stackId == stack.mStackId) {
- return stack;
- }
- }
- return null;
- }
-
- int getStackPosition(int stackId) {
- for (Integer displayId : mDisplayStacks.keySet()) {
- List<ActivityStack> stacks = mDisplayStacks.get(displayId);
- for (int i = 0; i < stacks.size(); i++) {
- if (stackId == stacks.get(i).mStackId) {
- return i;
- }
- }
- }
- return -1;
- }
-
- List<ActivityStack> getStacks() {
- return new ArrayList(mStacks);
- }
-
- int getStackCount() {
- return mStacks.size();
- }
-
- boolean containsActivity(String activityName) {
- for (ActivityStack stack : mStacks) {
- for (ActivityTask task : stack.mTasks) {
- for (Activity activity : task.mActivities) {
- if (activity.name.equals(activityName)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- boolean isActivityVisible(String activityName) {
- for (ActivityStack stack : mStacks) {
- for (ActivityTask task : stack.mTasks) {
- for (Activity activity : task.mActivities) {
- if (activity.name.equals(activityName)) {
- return activity.visible;
- }
- }
- }
- }
- return false;
- }
-
- boolean containsStartedActivities() {
- for (ActivityStack stack : mStacks) {
- for (ActivityTask task : stack.mTasks) {
- for (Activity activity : task.mActivities) {
- if (!activity.state.equals(STATE_STOPPED)
- && !activity.state.equals(STATE_DESTROYED)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- boolean hasActivityState(String activityName, String activityState) {
- String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
- for (ActivityStack stack : mStacks) {
- for (ActivityTask task : stack.mTasks) {
- for (Activity activity : task.mActivities) {
- if (activity.name.equals(fullName)) {
- return activity.state.equals(activityState);
- }
- }
- }
- }
- return false;
- }
-
- int getActivityProcId(String activityName) {
- for (ActivityStack stack : mStacks) {
- for (ActivityTask task : stack.mTasks) {
- for (Activity activity : task.mActivities) {
- if (activity.name.equals(activityName)) {
- return activity.procId;
- }
- }
- }
- }
- return -1;
- }
-
- boolean isHomeActivityVisible() {
- final Activity homeActivity = getHomeActivity();
- return homeActivity != null && homeActivity.visible;
- }
-
- boolean isRecentsActivityVisible() {
- final Activity recentsActivity = getRecentsActivity();
- return recentsActivity != null && recentsActivity.visible;
- }
-
- String getHomeActivityName() {
- Activity activity = getHomeActivity();
- if (activity == null) {
- return null;
- }
- return activity.name;
- }
-
- ActivityTask getHomeTask() {
- ActivityStack homeStack = getStackById(HOME_STACK_ID);
- if (homeStack != null) {
- for (ActivityTask task : homeStack.mTasks) {
- if (task.mTaskType == HOME_ACTIVITY_TYPE) {
- return task;
- }
- }
- return null;
- }
- return null;
- }
-
- ActivityTask getRecentsTask() {
- ActivityStack recentsStack = getStackById(RECENTS_STACK_ID);
- if (recentsStack != null) {
- for (ActivityTask task : recentsStack.mTasks) {
- if (task.mTaskType == RECENTS_ACTIVITY_TYPE) {
- return task;
- }
- }
- return null;
- }
- return null;
- }
-
- private Activity getHomeActivity() {
- final ActivityTask homeTask = getHomeTask();
- return homeTask != null ? homeTask.mActivities.get(homeTask.mActivities.size() - 1) : null;
- }
-
- private Activity getRecentsActivity() {
- final ActivityTask recentsTask = getRecentsTask();
- return recentsTask != null ? recentsTask.mActivities.get(recentsTask.mActivities.size() - 1)
- : null;
- }
-
- ActivityTask getTaskByActivityName(String activityName) {
- return getTaskByActivityName(activityName, -1);
- }
-
- ActivityTask getTaskByActivityName(String activityName, int stackId) {
- String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
- for (ActivityStack stack : mStacks) {
- if (stackId == -1 || stackId == stack.mStackId) {
- for (ActivityTask task : stack.mTasks) {
- for (Activity activity : task.mActivities) {
- if (activity.name.equals(fullName)) {
- return task;
- }
- }
- }
- }
- }
- return null;
- }
-
- static class ActivityStack extends ActivityContainer {
-
- private static final Pattern TASK_ID_PATTERN = Pattern.compile("Task id #(\\d+)");
- private static final Pattern RESUMED_ACTIVITY_PATTERN = Pattern.compile(
- "mResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}");
- private static final Pattern SLEEPING_PATTERN = Pattern.compile("isSleeping=(\\S+)");
-
- int mDisplayId;
- int mStackId;
- String mResumedActivity;
- Boolean mSleeping; // A Boolean to trigger an NPE if it's not initialized
- ArrayList<ActivityTask> mTasks = new ArrayList();
-
- private ActivityStack() {
- }
-
- static ActivityStack create(LinkedList<String> dump, Pattern stackIdPattern,
- Pattern[] exitPatterns, int displayId) {
- final String line = dump.peek().trim();
-
- final Matcher matcher = stackIdPattern.matcher(line);
- if (!matcher.matches()) {
- // Not a stack.
- return null;
- }
- // For the stack Id line we just read.
- dump.pop();
-
- final ActivityStack stack = new ActivityStack();
- stack.mDisplayId = displayId;
- log(line);
- final String stackId = matcher.group(1);
- log(stackId);
- stack.mStackId = Integer.parseInt(stackId);
- stack.extract(dump, exitPatterns);
- return stack;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
- final List<Pattern> taskExitPatterns = new ArrayList();
- Collections.addAll(taskExitPatterns, exitPatterns);
- taskExitPatterns.add(TASK_ID_PATTERN);
- taskExitPatterns.add(RESUMED_ACTIVITY_PATTERN);
- final Pattern[] taskExitPatternsArray =
- taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
-
- while (!doneExtracting(dump, exitPatterns)) {
- final ActivityTask task =
- ActivityTask.create(dump, TASK_ID_PATTERN, taskExitPatternsArray);
-
- if (task != null) {
- mTasks.add(task);
- continue;
- }
-
- final String line = dump.pop().trim();
-
- if (extractFullscreen(line)) {
- continue;
- }
-
- if (extractBounds(line)) {
- continue;
- }
-
- Matcher matcher = RESUMED_ACTIVITY_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- mResumedActivity = matcher.group(3);
- log(mResumedActivity);
- continue;
- }
-
- matcher = SLEEPING_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- mSleeping = "true".equals(matcher.group(1));
- continue;
- }
- }
- }
-
- /**
- * @return the bottom task in the stack.
- */
- ActivityTask getBottomTask() {
- if (!mTasks.isEmpty()) {
- // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
- // so the indices are inverted
- return mTasks.get(mTasks.size() - 1);
- }
- return null;
- }
-
- /**
- * @return the top task in the stack.
- */
- ActivityTask getTopTask() {
- if (!mTasks.isEmpty()) {
- // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
- // so the indices are inverted
- return mTasks.get(0);
- }
- return null;
- }
-
- List<ActivityTask> getTasks() {
- return new ArrayList(mTasks);
- }
-
- ActivityTask getTask(int taskId) {
- for (ActivityTask task : mTasks) {
- if (taskId == task.mTaskId) {
- return task;
- }
- }
- return null;
- }
- }
-
- static class ActivityTask extends ActivityContainer {
- private static final Pattern TASK_RECORD_PATTERN = Pattern.compile("\\* TaskRecord\\"
- + "{(\\S+) #(\\d+) (\\S+)=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}");
-
- private static final Pattern LAST_NON_FULLSCREEN_BOUNDS_PATTERN = Pattern.compile(
- "mLastNonFullscreenBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)");
-
- private static final Pattern ORIG_ACTIVITY_PATTERN = Pattern.compile("origActivity=(\\S+)");
- private static final Pattern REAL_ACTIVITY_PATTERN = Pattern.compile("realActivity=(\\S+)");
-
- private static final Pattern ACTIVITY_NAME_PATTERN = Pattern.compile(
- "\\* Hist #(\\d+)\\: ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}");
-
- private static final Pattern TASK_TYPE_PATTERN = Pattern.compile("autoRemoveRecents=(\\S+) "
- + "isPersistable=(\\S+) numFullscreen=(\\d+) taskType=(\\d+) "
- + "mTaskToReturnTo=(\\d+)");
-
- private static final Pattern RESIZABLE_PATTERN = Pattern.compile(
- ".*mResizeMode=([^\\s]+).*");
-
- int mTaskId;
- int mStackId;
- Rectangle mLastNonFullscreenBounds;
- String mRealActivity;
- String mOrigActivity;
- ArrayList<Activity> mActivities = new ArrayList();
- int mTaskType = -1;
- int mReturnToType = -1;
- private String mResizeMode;
-
- private ActivityTask() {
- }
-
- static ActivityTask create(
- LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) {
- final String line = dump.peek().trim();
-
- final Matcher matcher = taskIdPattern.matcher(line);
- if (!matcher.matches()) {
- // Not a task.
- return null;
- }
- // For the task Id line we just read.
- dump.pop();
-
- final ActivityTask task = new ActivityTask();
- log(line);
- final String taskId = matcher.group(1);
- log(taskId);
- task.mTaskId = Integer.parseInt(taskId);
- task.extract(dump, exitPatterns);
- return task;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
- final List<Pattern> activityExitPatterns = new ArrayList();
- Collections.addAll(activityExitPatterns, exitPatterns);
- activityExitPatterns.add(ACTIVITY_NAME_PATTERN);
- final Pattern[] activityExitPatternsArray =
- activityExitPatterns.toArray(new Pattern[activityExitPatterns.size()]);
-
- while (!doneExtracting(dump, exitPatterns)) {
- final Activity activity =
- Activity.create(dump, ACTIVITY_NAME_PATTERN, activityExitPatternsArray);
-
- if (activity != null) {
- mActivities.add(activity);
- continue;
- }
-
- final String line = dump.pop().trim();
-
- if (extractFullscreen(line)) {
- continue;
- }
-
- if (extractBounds(line)) {
- continue;
- }
-
- if (extractMinimalSize(line)) {
- continue;
- }
-
- Matcher matcher = TASK_RECORD_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String stackId = matcher.group(6);
- mStackId = Integer.valueOf(stackId);
- log(stackId);
- continue;
- }
-
- matcher = LAST_NON_FULLSCREEN_BOUNDS_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- mLastNonFullscreenBounds = extractBounds(matcher);
- }
-
- matcher = REAL_ACTIVITY_PATTERN.matcher(line);
- if (matcher.matches()) {
- if (mRealActivity == null) {
- log(line);
- mRealActivity = matcher.group(1);
- log(mRealActivity);
- }
- continue;
- }
-
- matcher = ORIG_ACTIVITY_PATTERN.matcher(line);
- if (matcher.matches()) {
- if (mOrigActivity == null) {
- log(line);
- mOrigActivity = matcher.group(1);
- log(mOrigActivity);
- }
- continue;
- }
-
- matcher = TASK_TYPE_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- mTaskType = Integer.valueOf(matcher.group(4));
- mReturnToType = Integer.valueOf(matcher.group(5));
- continue;
- }
-
- matcher = RESIZABLE_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- mResizeMode = matcher.group(1);
- log(mResizeMode);
- continue;
- }
- }
- }
-
- public String getResizeMode() {
- return mResizeMode;
- }
-
- /**
- * @return whether this task contains the given activity.
- */
- public boolean containsActivity(String activityName) {
- for (Activity activity : mActivities) {
- if (activity.name.equals(activityName)) {
- return true;
- }
- }
- return false;
- }
- }
-
- static class Activity {
- private static final Pattern STATE_PATTERN = Pattern.compile("state=(\\S+).*");
- private static final Pattern VISIBILITY_PATTERN = Pattern.compile("keysPaused=(\\S+) "
- + "inHistory=(\\S+) visible=(\\S+) sleeping=(\\S+) idle=(\\S+) "
- + "mStartingWindowState=(\\S+)");
- private static final Pattern FRONT_OF_TASK_PATTERN = Pattern.compile("frontOfTask=(\\S+) "
- + "task=TaskRecord\\{(\\S+) #(\\d+) A=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}");
- private static final Pattern PROCESS_RECORD_PATTERN = Pattern.compile(
- "app=ProcessRecord\\{(\\S+) (\\d+):(\\S+)/(.+)\\}");
-
- String name;
- String state;
- boolean visible;
- boolean frontOfTask;
- int procId = -1;
-
- private Activity() {
- }
-
- static Activity create(
- LinkedList<String> dump, Pattern activityNamePattern, Pattern[] exitPatterns) {
- final String line = dump.peek().trim();
-
- final Matcher matcher = activityNamePattern.matcher(line);
- if (!matcher.matches()) {
- // Not an activity.
- return null;
- }
- // For the activity name line we just read.
- dump.pop();
-
- final Activity activity = new Activity();
- log(line);
- activity.name = matcher.group(4);
- log(activity.name);
- activity.extract(dump, exitPatterns);
- return activity;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
- while (!doneExtracting(dump, exitPatterns)) {
- final String line = dump.pop().trim();
-
- // Break the activity extraction once we hit an empty line
- if (line.isEmpty()) {
- break;
- }
-
- Matcher matcher = VISIBILITY_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String visibleString = matcher.group(3);
- visible = Boolean.valueOf(visibleString);
- log(visibleString);
- continue;
- }
-
- matcher = STATE_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- state = matcher.group(1);
- log(state);
- continue;
- }
-
- matcher = PROCESS_RECORD_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String procIdString = matcher.group(2);
- procId = Integer.valueOf(procIdString);
- log(procIdString);
- continue;
- }
-
- matcher = FRONT_OF_TASK_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String frontOfTaskString = matcher.group(1);
- frontOfTask = Boolean.valueOf(frontOfTaskString);
- log(frontOfTaskString);
- continue;
- }
- }
- }
- }
-
- static abstract class ActivityContainer {
- protected static final Pattern FULLSCREEN_PATTERN = Pattern.compile("mFullscreen=(\\S+)");
- protected static final Pattern BOUNDS_PATTERN =
- Pattern.compile("mBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)");
- protected static final Pattern MIN_WIDTH_PATTERN =
- Pattern.compile("mMinWidth=(\\d+)");
- protected static final Pattern MIN_HEIGHT_PATTERN =
- Pattern.compile("mMinHeight=(\\d+)");
-
- protected boolean mFullscreen;
- protected Rectangle mBounds;
- protected int mMinWidth = -1;
- protected int mMinHeight = -1;
-
- boolean extractFullscreen(String line) {
- final Matcher matcher = FULLSCREEN_PATTERN.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- log(line);
- final String fullscreen = matcher.group(1);
- log(fullscreen);
- mFullscreen = Boolean.valueOf(fullscreen);
- return true;
- }
-
- boolean extractBounds(String line) {
- final Matcher matcher = BOUNDS_PATTERN.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- log(line);
- mBounds = extractBounds(matcher);
- return true;
- }
-
- static Rectangle extractBounds(Matcher matcher) {
- final int left = Integer.valueOf(matcher.group(1));
- final int top = Integer.valueOf(matcher.group(2));
- final int right = Integer.valueOf(matcher.group(3));
- final int bottom = Integer.valueOf(matcher.group(4));
- final Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
-
- log(rect.toString());
- return rect;
- }
-
- boolean extractMinimalSize(String line) {
- final Matcher minWidthMatcher = MIN_WIDTH_PATTERN.matcher(line);
- final Matcher minHeightMatcher = MIN_HEIGHT_PATTERN.matcher(line);
-
- if (minWidthMatcher.matches()) {
- log(line);
- mMinWidth = Integer.valueOf(minWidthMatcher.group(1));
- } else if (minHeightMatcher.matches()) {
- log(line);
- mMinHeight = Integer.valueOf(minHeightMatcher.group(1));
- } else {
- return false;
- }
- return true;
- }
-
- Rectangle getBounds() {
- return mBounds;
- }
-
- boolean isFullscreen() {
- return mFullscreen;
- }
-
- int getMinWidth() {
- return mMinWidth;
- }
-
- int getMinHeight() {
- return mMinHeight;
- }
- }
-
- static class KeyguardControllerState {
- private static final Pattern NAME_PATTERN = Pattern.compile("KeyguardController:");
- private static final Pattern SHOWING_PATTERN = Pattern.compile("mKeyguardShowing=(\\S+)");
- private static final Pattern OCCLUDED_PATTERN = Pattern.compile("mOccluded=(\\S+)");
-
- boolean keyguardShowing;
- boolean keyguardOccluded;
-
- private KeyguardControllerState() {
- }
-
- static KeyguardControllerState create(LinkedList<String> dump, Pattern[] exitPatterns) {
- final String line = dump.peek().trim();
-
- final Matcher matcher = NAME_PATTERN.matcher(line);
- if (!matcher.matches()) {
- // Not KeyguardController
- return null;
- }
-
- // For the KeyguardController line we just read.
- dump.pop();
-
- final KeyguardControllerState controller = new KeyguardControllerState();
- controller.extract(dump, exitPatterns);
- return controller;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
- while (!doneExtracting(dump, exitPatterns)) {
- final String line = dump.pop().trim();
-
- Matcher matcher = SHOWING_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String showingString = matcher.group(1);
- keyguardShowing = Boolean.valueOf(showingString);
- log(showingString);
- continue;
- }
-
- matcher = OCCLUDED_PATTERN.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String occludedString = matcher.group(1);
- keyguardOccluded = Boolean.valueOf(occludedString);
- log(occludedString);
- continue;
- }
- }
- }
- }
-
- static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) {
- if (dump.isEmpty()) {
- return true;
- }
- final String line = dump.peek().trim();
-
- for (Pattern pattern : exitPatterns) {
- if (pattern.matcher(line).matches()) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
deleted file mode 100644
index 9ba292f..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ /dev/null
@@ -1,1459 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import java.awt.image.BufferedImage;
-import java.lang.Exception;
-import java.lang.Integer;
-import java.lang.String;
-import java.util.HashSet;
-import java.util.List;
-import java.util.UUID;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import android.server.cts.ActivityManagerState.ActivityStack;
-
-import javax.imageio.ImageIO;
-
-public abstract class ActivityManagerTestBase extends DeviceTestCase {
- private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
- private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
- private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
-
- // Constants copied from ActivityManager.StackId. If they are changed there, these must be
- // updated.
- /** Invalid stack ID. */
- public static final int INVALID_STACK_ID = -1;
-
- /** First static stack ID. */
- public static final int FIRST_STATIC_STACK_ID = 0;
-
- /** Home activity stack ID. */
- public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
-
- /** ID of stack where fullscreen activities are normally launched into. */
- public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
- /** ID of stack where freeform/resized activities are normally launched into. */
- public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
- /** ID of stack that occupies a dedicated region of the screen. */
- public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
- /** ID of stack that always on top (always visible) when it exist. */
- public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
- /** Recents activity stack ID. */
- public static final int RECENTS_STACK_ID = PINNED_STACK_ID + 1;
-
- /** Assistant activity stack ID. This stack is fullscreen and non-resizeable. */
- public static final int ASSISTANT_STACK_ID = RECENTS_STACK_ID + 1;
-
- protected static final int[] ALL_STACK_IDS_BUT_HOME = {
- FULLSCREEN_WORKSPACE_STACK_ID, FREEFORM_WORKSPACE_STACK_ID, DOCKED_STACK_ID,
- PINNED_STACK_ID, ASSISTANT_STACK_ID
- };
-
- protected static final int[] ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN = {
- FREEFORM_WORKSPACE_STACK_ID, DOCKED_STACK_ID, PINNED_STACK_ID, ASSISTANT_STACK_ID
- };
-
- private static final String TASK_ID_PREFIX = "taskId";
-
- private static final String AM_STACK_LIST = "am stack list";
-
- private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.cts";
- private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
- = "am force-stop android.server.cts.second";
- private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
- = "am force-stop android.server.cts.third";
-
- private static final String AM_REMOVE_STACK = "am stack remove ";
-
- protected static final String AM_START_HOME_ACTIVITY_COMMAND =
- "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
-
- protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
- "am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
-
- static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
- static final String ALT_LAUNCHING_ACTIVITY = "AltLaunchingActivity";
- static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
-
- /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
- static final String FINISH_ACTIVITY_BROADCAST
- = "am broadcast -a trigger_broadcast --ez finish true";
-
- /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
- static final String MOVE_TASK_TO_BACK_BROADCAST
- = "am broadcast -a trigger_broadcast --ez moveToBack true";
-
- private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
- private static final String AM_RESIZE_STACK = "am stack resize ";
-
- static final String AM_MOVE_TASK = "am stack move-task ";
-
- private static final String AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW =
- "am supports-split-screen-multi-window";
- private static final String AM_NO_HOME_SCREEN = "am no-home-screen";
-
- private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
- private static final String INPUT_KEYEVENT_BACK = "input keyevent 4";
- private static final String INPUT_KEYEVENT_APP_SWITCH = "input keyevent 187";
- public static final String INPUT_KEYEVENT_WINDOW = "input keyevent 171";
-
- private static final String LOCK_CREDENTIAL = "1234";
-
- private static final int INVALID_DISPLAY_ID = -1;
-
- private static final String DEFAULT_COMPONENT_NAME = "android.server.cts";
-
- private static final int UI_MODE_TYPE_MASK = 0x0f;
- private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
-
- static String componentName = DEFAULT_COMPONENT_NAME;
-
- protected static final int INVALID_DEVICE_ROTATION = -1;
-
- /** A reference to the device under test. */
- protected ITestDevice mDevice;
-
- private HashSet<String> mAvailableFeatures;
-
- private boolean mLockCredentialsSet;
-
- private boolean mLockDisabled;
-
- protected static String getAmStartCmd(final String activityName) {
- return "am start -n " + getActivityComponentName(activityName);
- }
-
- /**
- * @return the am command to start the given activity with the following extra key/value pairs.
- * {@param keyValuePairs} must be a list of arguments defining each key/value extra.
- */
- protected static String getAmStartCmd(final String activityName,
- final String... keyValuePairs) {
- String base = getAmStartCmd(activityName);
- if (keyValuePairs.length % 2 != 0) {
- throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
- }
- for (int i = 0; i < keyValuePairs.length; i += 2) {
- base += " --es " + keyValuePairs[i] + " " + keyValuePairs[i + 1];
- }
- return base;
- }
-
- protected static String getAmStartCmd(final String activityName, final int displayId) {
- return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
- + " --display " + displayId;
- }
-
- protected static String getAmStartCmdInNewTask(final String activityName) {
- return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000";
- }
-
- protected static String getAmStartCmdOverHome(final String activityName) {
- return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
- }
-
- protected static String getOrientationBroadcast(int orientation) {
- return "am broadcast -a trigger_broadcast --ei orientation " + orientation;
- }
-
- static String getActivityComponentName(final String activityName) {
- return getActivityComponentName(componentName, activityName);
- }
-
- private static boolean isFullyQualifiedActivityName(String name) {
- return name != null && name.contains(".");
- }
-
- static String getActivityComponentName(final String packageName, final String activityName) {
- return packageName + "/" + (isFullyQualifiedActivityName(activityName) ? "" : ".") +
- activityName;
- }
-
- // A little ugly, but lets avoid having to strip static everywhere for
- // now.
- public static void setComponentName(String name) {
- componentName = name;
- }
-
- protected static void setDefaultComponentName() {
- setComponentName(DEFAULT_COMPONENT_NAME);
- }
-
- static String getBaseWindowName() {
- return getBaseWindowName(componentName);
- }
-
- static String getBaseWindowName(final String packageName) {
- return getBaseWindowName(packageName, true /*prependPackageName*/);
- }
-
- static String getBaseWindowName(final String packageName, boolean prependPackageName) {
- return packageName + "/" + (prependPackageName ? packageName + "." : "");
- }
-
- static String getWindowName(final String activityName) {
- return getWindowName(componentName, activityName);
- }
-
- static String getWindowName(final String packageName, final String activityName) {
- return getBaseWindowName(packageName, !isFullyQualifiedActivityName(activityName))
- + activityName;
- }
-
- protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
-
- private int mInitialAccelerometerRotation;
- private int mUserRotation;
- private float mFontScale;
-
- private SurfaceTraceReceiver mSurfaceTraceReceiver;
- private Thread mSurfaceTraceThread;
-
- void installSurfaceObserver(SurfaceTraceReceiver.SurfaceObserver observer) {
- mSurfaceTraceReceiver = new SurfaceTraceReceiver(observer);
- mSurfaceTraceThread = new Thread() {
- @Override
- public void run() {
- try {
- mDevice.executeShellCommand("wm surface-trace", mSurfaceTraceReceiver);
- } catch (DeviceNotAvailableException e) {
- logE("Device not available: " + e.toString());
- }
- }
- };
- mSurfaceTraceThread.start();
- }
-
- void removeSurfaceObserver() {
- mSurfaceTraceReceiver.cancel();
- mSurfaceTraceThread.interrupt();
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- setDefaultComponentName();
-
- // Get the device, this gives a handle to run commands and install APKs.
- mDevice = getDevice();
- wakeUpAndUnlockDevice();
- pressHomeButton();
- // Remove special stacks.
- removeStacks(ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN);
- // Store rotation settings.
- mInitialAccelerometerRotation = getAccelerometerRotation();
- mUserRotation = getUserRotation();
- mFontScale = getFontScale();
- mLockCredentialsSet = false;
- mLockDisabled = isLockDisabled();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- try {
- setLockDisabled(mLockDisabled);
- executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
- executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
- executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
- // Restore rotation settings to the state they were before test.
- setAccelerometerRotation(mInitialAccelerometerRotation);
- setUserRotation(mUserRotation);
- setFontScale(mFontScale);
- setWindowTransitionAnimationDurationScale(1);
- // Remove special stacks.
- removeStacks(ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN);
- wakeUpAndUnlockDevice();
- pressHomeButton();
- } catch (DeviceNotAvailableException e) {
- }
- }
-
- protected void removeStacks(int... stackIds) {
- try {
- for (Integer stackId : stackIds) {
- executeShellCommand(AM_REMOVE_STACK + stackId);
- }
- } catch (DeviceNotAvailableException e) {
- }
- }
-
- protected String executeShellCommand(String command) throws DeviceNotAvailableException {
- return executeShellCommand(mDevice, command);
- }
-
- protected static String executeShellCommand(ITestDevice device, String command)
- throws DeviceNotAvailableException {
- log("adb shell " + command);
- return device.executeShellCommand(command);
- }
-
- protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
- throws DeviceNotAvailableException {
- log("adb shell " + command);
- mDevice.executeShellCommand(command, outputReceiver);
- }
-
- protected BufferedImage takeScreenshot() throws Exception {
- final InputStreamSource stream = mDevice.getScreenshot("PNG", false /* rescale */);
- if (stream == null) {
- fail("Failed to take screenshot of device");
- }
- return ImageIO.read(stream.createInputStream());
- }
-
- protected void launchActivityInComponent(final String componentName,
- final String targetActivityName, final String... keyValuePairs) throws Exception {
- final String originalComponentName = ActivityManagerTestBase.componentName;
- setComponentName(componentName);
- launchActivity(targetActivityName, keyValuePairs);
- setComponentName(originalComponentName);
- }
-
- protected void launchActivity(final String targetActivityName, final String... keyValuePairs)
- throws Exception {
- executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
- mAmWmState.waitForValidState(mDevice, targetActivityName);
- }
-
- protected void launchActivityNoWait(final String targetActivityName,
- final String... keyValuePairs) throws Exception {
- executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
- }
-
- protected void launchActivityInNewTask(final String targetActivityName) throws Exception {
- executeShellCommand(getAmStartCmdInNewTask(targetActivityName));
- mAmWmState.waitForValidState(mDevice, targetActivityName);
- }
-
- /**
- * Starts an activity in a new stack.
- * @return the stack id of the newly created stack.
- */
- protected int launchActivityInNewDynamicStack(final String activityName) throws Exception {
- HashSet<Integer> stackIds = getStackIds();
- executeShellCommand("am stack start " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
- + " " + getActivityComponentName(activityName));
- HashSet<Integer> newStackIds = getStackIds();
- newStackIds.removeAll(stackIds);
- if (newStackIds.isEmpty()) {
- return INVALID_STACK_ID;
- } else {
- assertTrue(newStackIds.size() == 1);
- return newStackIds.iterator().next();
- }
- }
-
- /**
- * Returns the set of stack ids.
- */
- private HashSet<Integer> getStackIds() throws Exception {
- mAmWmState.computeState(mDevice, null);
- final List<ActivityStack> stacks = mAmWmState.getAmState().getStacks();
- final HashSet<Integer> stackIds = new HashSet<>();
- for (ActivityStack s : stacks) {
- stackIds.add(s.mStackId);
- }
- return stackIds;
- }
-
- protected void launchHomeActivity()
- throws Exception {
- executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
- mAmWmState.waitForHomeActivityVisible(mDevice);
- }
-
- protected void launchActivityOnDisplay(String targetActivityName, int displayId)
- throws Exception {
- executeShellCommand(getAmStartCmd(targetActivityName, displayId));
-
- mAmWmState.waitForValidState(mDevice, targetActivityName);
- }
-
- /**
- * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
- * that one should be started first.
- * @param toSide Launch to side in split-screen.
- * @param randomData Make intent URI random by generating random data.
- * @param multipleTask Allow multiple task launch.
- * @param targetActivityName Target activity to be launched. Only class name should be provided,
- * package name of {@link #LAUNCHING_ACTIVITY} will be added
- * automatically.
- * @param displayId Display id where target activity should be launched.
- * @throws Exception
- */
- protected void launchActivityFromLaunching(boolean toSide, boolean randomData,
- boolean multipleTask, String targetActivityName, int displayId) throws Exception {
- StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
- commandBuilder.append(" -f 0x20000000");
- if (toSide) {
- commandBuilder.append(" --ez launch_to_the_side true");
- }
- if (randomData) {
- commandBuilder.append(" --ez random_data true");
- }
- if (multipleTask) {
- commandBuilder.append(" --ez multiple_task true");
- }
- if (targetActivityName != null) {
- commandBuilder.append(" --es target_activity ").append(targetActivityName);
- }
- if (displayId != INVALID_DISPLAY_ID) {
- commandBuilder.append(" --ei display_id ").append(displayId);
- }
- executeShellCommand(commandBuilder.toString());
-
- mAmWmState.waitForValidState(mDevice, targetActivityName);
- }
-
- protected void launchActivityInStack(String activityName, int stackId,
- final String... keyValuePairs) throws Exception {
- executeShellCommand(getAmStartCmd(activityName, keyValuePairs) + " --stack " + stackId);
-
- mAmWmState.waitForValidState(mDevice, activityName, stackId);
- }
-
- protected void launchActivityInDockStack(String activityName) throws Exception {
- launchActivity(activityName);
- // TODO(b/36279415): The way we launch an activity into the docked stack is different from
- // what the user actually does. Long term we should use
- // "adb shell input keyevent --longpress _app_swich_key_code_" to trigger a long press on
- // the recents button which is consistent with what the user does. However, currently sys-ui
- // does handle FLAG_LONG_PRESS for the app switch key. It just listens for long press on the
- // view. We need to fix that in sys-ui before we can change this.
- moveActivityToDockStack(activityName);
-
- mAmWmState.waitForValidState(mDevice, activityName, DOCKED_STACK_ID);
- }
-
- protected void launchActivityToSide(boolean randomData, boolean multipleTaskFlag,
- String targetActivity) throws Exception {
- final String activityToLaunch = targetActivity != null ? targetActivity : "TestActivity";
- getLaunchActivityBuilder().setToSide(true).setRandomData(randomData)
- .setMultipleTask(multipleTaskFlag).setTargetActivityName(activityToLaunch)
- .execute();
-
- mAmWmState.waitForValidState(mDevice, activityToLaunch, FULLSCREEN_WORKSPACE_STACK_ID);
- }
-
- protected void moveActivityToDockStack(String activityName) throws Exception {
- moveActivityToStack(activityName, DOCKED_STACK_ID);
- }
-
- protected void moveActivityToStack(String activityName, int stackId) throws Exception {
- final int taskId = getActivityTaskId(activityName);
- final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
- executeShellCommand(cmd);
-
- mAmWmState.waitForValidState(mDevice, activityName, stackId);
- }
-
- protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
- throws Exception {
- final int taskId = getActivityTaskId(activityName);
- final String cmd = "am task resize "
- + taskId + " " + left + " " + top + " " + right + " " + bottom;
- executeShellCommand(cmd);
- }
-
- protected void resizeDockedStack(
- int stackWidth, int stackHeight, int taskWidth, int taskHeight)
- throws DeviceNotAvailableException {
- executeShellCommand(AM_RESIZE_DOCKED_STACK
- + "0 0 " + stackWidth + " " + stackHeight
- + " 0 0 " + taskWidth + " " + taskHeight);
- }
-
- protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
- int stackHeight) throws DeviceNotAvailableException {
- executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
- stackTop, stackWidth, stackHeight));
- }
-
- protected void pressHomeButton() throws DeviceNotAvailableException {
- executeShellCommand(INPUT_KEYEVENT_HOME);
- }
-
- protected void pressBackButton() throws DeviceNotAvailableException {
- executeShellCommand(INPUT_KEYEVENT_BACK);
- }
-
- protected void pressAppSwitchButton() throws DeviceNotAvailableException {
- executeShellCommand(INPUT_KEYEVENT_APP_SWITCH);
- }
-
- // Utility method for debugging, not used directly here, but useful, so kept around.
- protected void printStacksAndTasks() throws DeviceNotAvailableException {
- CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- executeShellCommand(AM_STACK_LIST, outputReceiver);
- String output = outputReceiver.getOutput();
- for (String line : output.split("\\n")) {
- CLog.logAndDisplay(LogLevel.INFO, line);
- }
- }
-
- protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
- CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- executeShellCommand(AM_STACK_LIST, outputReceiver);
- final String output = outputReceiver.getOutput();
- final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
- for (String line : output.split("\\n")) {
- Matcher matcher = activityPattern.matcher(line);
- if (matcher.matches()) {
- for (String word : line.split("\\s+")) {
- if (word.startsWith(TASK_ID_PREFIX)) {
- final String withColon = word.split("=")[1];
- return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
- }
- }
- }
- }
- return -1;
- }
-
- protected boolean supportsVrMode() throws DeviceNotAvailableException {
- return hasDeviceFeature("android.software.vr.mode") &&
- hasDeviceFeature("android.hardware.vr.high_performance");
- }
-
- protected boolean supportsPip() throws DeviceNotAvailableException {
- return hasDeviceFeature("android.software.picture_in_picture")
- || PRETEND_DEVICE_SUPPORTS_PIP;
- }
-
- protected boolean supportsFreeform() throws DeviceNotAvailableException {
- return hasDeviceFeature("android.software.freeform_window_management")
- || PRETEND_DEVICE_SUPPORTS_FREEFORM;
- }
-
- protected boolean isHandheld() throws DeviceNotAvailableException {
- return !hasDeviceFeature("android.software.leanback")
- && !hasDeviceFeature("android.hardware.type.watch")
- && !hasDeviceFeature("android.hardware.type.embedded");
- }
-
- // TODO: Switch to using a feature flag, when available.
- protected boolean isUiModeLockedToVrHeadset() throws DeviceNotAvailableException {
- final String output = runCommandAndPrintOutput("dumpsys uimode");
-
- Integer curUiMode = null;
- Boolean uiModeLocked = null;
- for (String line : output.split("\\n")) {
- line = line.trim();
- Matcher matcher = sCurrentUiModePattern.matcher(line);
- if (matcher.find()) {
- curUiMode = Integer.parseInt(matcher.group(1), 16);
- }
- matcher = sUiModeLockedPattern.matcher(line);
- if (matcher.find()) {
- uiModeLocked = matcher.group(1).equals("true");
- }
- }
-
- boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
- && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
-
- if (uiModeLockedToVrHeadset) {
- CLog.logAndDisplay(LogLevel.INFO, "UI mode is locked to VR headset");
- }
-
- return uiModeLockedToVrHeadset;
- }
-
- protected boolean supportsSplitScreenMultiWindow() throws DeviceNotAvailableException {
- CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- executeShellCommand(AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW, outputReceiver);
- String output = outputReceiver.getOutput();
- return !output.startsWith("false");
- }
-
- protected boolean noHomeScreen() throws DeviceNotAvailableException {
- CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- executeShellCommand(AM_NO_HOME_SCREEN, outputReceiver);
- String output = outputReceiver.getOutput();
- return output.startsWith("true");
- }
-
- /**
- * Rotation support is indicated by explicitly having both landscape and portrait
- * features or not listing either at all.
- */
- protected boolean supportsRotation() throws DeviceNotAvailableException {
- return (hasDeviceFeature("android.hardware.screen.landscape")
- && hasDeviceFeature("android.hardware.screen.portrait"))
- || (!hasDeviceFeature("android.hardware.screen.landscape")
- && !hasDeviceFeature("android.hardware.screen.portrait"));
- }
-
- protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
- if (mAvailableFeatures == null) {
- // TODO: Move this logic to ITestDevice.
- final String output = runCommandAndPrintOutput("pm list features");
-
- // Extract the id of the new user.
- mAvailableFeatures = new HashSet<>();
- for (String feature: output.split("\\s+")) {
- // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
- String[] tokens = feature.split(":");
- assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
- tokens.length > 1);
- assertEquals(feature, "feature", tokens[0]);
- mAvailableFeatures.add(tokens[1]);
- }
- }
- boolean result = mAvailableFeatures.contains(requiredFeature);
- if (!result) {
- CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
- }
- return result;
- }
-
- protected boolean isDisplayOn() throws DeviceNotAvailableException {
- final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- mDevice.executeShellCommand("dumpsys power", outputReceiver);
-
- for (String line : outputReceiver.getOutput().split("\\n")) {
- line = line.trim();
-
- final Matcher matcher = sDisplayStatePattern.matcher(line);
- if (matcher.matches()) {
- final String state = matcher.group(1);
- log("power state=" + state);
- return "ON".equals(state);
- }
- }
- log("power state :(");
- return false;
- }
-
- protected void sleepDevice() throws DeviceNotAvailableException {
- int retriesLeft = 5;
- runCommandAndPrintOutput("input keyevent SLEEP");
- do {
- if (isDisplayOn()) {
- log("***Waiting for display to turn off...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
- }
-
- protected void wakeUpAndUnlockDevice() throws DeviceNotAvailableException {
- wakeUpDevice();
- unlockDevice();
- }
-
- protected void wakeUpAndRemoveLock() throws DeviceNotAvailableException {
- wakeUpDevice();
- setLockDisabled(true);
- }
-
- protected void wakeUpDevice() throws DeviceNotAvailableException {
- runCommandAndPrintOutput("input keyevent WAKEUP");
- }
-
- protected void unlockDevice() throws DeviceNotAvailableException {
- runCommandAndPrintOutput("input keyevent 82");
- }
-
- protected void unlockDeviceWithCredential() throws Exception {
- runCommandAndPrintOutput("input keyevent 82");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- //ignored
- }
- enterAndConfirmLockCredential();
- }
-
- protected void enterAndConfirmLockCredential() throws Exception {
- // TODO: This should use waitForIdle..but there ain't such a thing on hostside tests, boo :(
- Thread.sleep(500);
-
- runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
- runCommandAndPrintOutput("input keyevent KEYCODE_ENTER");
- }
-
- protected void gotoKeyguard() throws Exception {
- sleepDevice();
- wakeUpDevice();
- mAmWmState.waitForKeyguardShowingAndNotOccluded(mDevice);
- }
-
- protected void setLockCredential() throws DeviceNotAvailableException {
- mLockCredentialsSet = true;
- runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
- }
-
- private void removeLockCredential() throws DeviceNotAvailableException {
- runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
- }
-
- /**
- * Returns whether the lock screen is disabled.
- * @return true if the lock screen is disabled, false otherwise.
- */
- private boolean isLockDisabled() throws DeviceNotAvailableException {
- final String isLockDisabled = runCommandAndPrintOutput("locksettings get-disabled").trim();
- if ("null".equals(isLockDisabled)) {
- return false;
- }
- return Boolean.parseBoolean(isLockDisabled);
-
- }
-
- /**
- * Disable the lock screen.
- * @param lockDisabled true if should disable, false otherwise.
- */
- void setLockDisabled(boolean lockDisabled) throws DeviceNotAvailableException {
- runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
- }
-
- /**
- * Sets the device rotation, value corresponds to one of {@link Surface.ROTATION_0},
- * {@link Surface.ROTATION_90}, {@link Surface.ROTATION_180}, {@link Surface.ROTATION_270}.
- */
- protected void setDeviceRotation(int rotation) throws Exception {
- setAccelerometerRotation(0);
- setUserRotation(rotation);
- mAmWmState.waitForRotation(mDevice, rotation);
- }
-
- protected int getDeviceRotation(int displayId) throws DeviceNotAvailableException {
- final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
- Pattern pattern = Pattern.compile(
- "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)"
- + "(rotation)(\\s+)(\\d+)");
- Matcher matcher = pattern.matcher(displays);
- while (matcher.find()) {
- final String match = matcher.group(7);
- return Integer.parseInt(match);
- }
-
- return INVALID_DEVICE_ROTATION;
- }
-
- private int getAccelerometerRotation() throws DeviceNotAvailableException {
- final String rotation =
- runCommandAndPrintOutput("settings get system accelerometer_rotation");
- return Integer.parseInt(rotation.trim());
- }
-
- private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
- runCommandAndPrintOutput(
- "settings put system accelerometer_rotation " + rotation);
- }
-
- protected int getUserRotation() throws DeviceNotAvailableException {
- final String rotation =
- runCommandAndPrintOutput("settings get system user_rotation").trim();
- if ("null".equals(rotation)) {
- return -1;
- }
- return Integer.parseInt(rotation);
- }
-
- private void setUserRotation(int rotation) throws DeviceNotAvailableException {
- if (rotation == -1) {
- runCommandAndPrintOutput(
- "settings delete system user_rotation");
- } else {
- runCommandAndPrintOutput(
- "settings put system user_rotation " + rotation);
- }
- }
-
- protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
- if (fontScale == 0.0f) {
- runCommandAndPrintOutput(
- "settings delete system font_scale");
- } else {
- runCommandAndPrintOutput(
- "settings put system font_scale " + fontScale);
- }
- }
-
- protected void setWindowTransitionAnimationDurationScale(float animDurationScale)
- throws DeviceNotAvailableException {
- runCommandAndPrintOutput(
- "settings put global transition_animation_scale " + animDurationScale);
- }
-
- protected float getFontScale() throws DeviceNotAvailableException {
- try {
- final String fontScale =
- runCommandAndPrintOutput("settings get system font_scale").trim();
- return Float.parseFloat(fontScale);
- } catch (NumberFormatException e) {
- // If we don't have a valid font scale key, return 0.0f now so
- // that we delete the key in tearDown().
- return 0.0f;
- }
- }
-
- protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
- final String output = executeShellCommand(command);
- log(output);
- return output;
- }
-
- /**
- * Tries to clear logcat and inserts log separator in case clearing didn't succeed, so we can
- * always find the starting point from where to evaluate following logs.
- * @return Unique log separator.
- */
- protected String clearLogcat() throws DeviceNotAvailableException {
- mDevice.executeAdbCommand("logcat", "-c");
- final String uniqueString = UUID.randomUUID().toString();
- executeShellCommand("log -t " + LOG_SEPARATOR + " " + uniqueString);
- return uniqueString;
- }
-
- void assertActivityLifecycle(String activityName, boolean relaunched,
- String logSeparator) throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = verifyLifecycleCondition(activityName, logSeparator, relaunched);
- if (resultString != null) {
- log("***Waiting for valid lifecycle state: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- /** @return Error string if lifecycle counts don't match, null if everything is fine. */
- private String verifyLifecycleCondition(String activityName, String logSeparator,
- boolean relaunched) throws DeviceNotAvailableException {
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
- logSeparator);
- if (relaunched) {
- if (lifecycleCounts.mDestroyCount < 1) {
- return activityName + " must have been destroyed. mDestroyCount="
- + lifecycleCounts.mDestroyCount;
- }
- if (lifecycleCounts.mCreateCount < 1) {
- return activityName + " must have been (re)created. mCreateCount="
- + lifecycleCounts.mCreateCount;
- }
- } else {
- if (lifecycleCounts.mDestroyCount > 0) {
- return activityName + " must *NOT* have been destroyed. mDestroyCount="
- + lifecycleCounts.mDestroyCount;
- }
- if (lifecycleCounts.mCreateCount > 0) {
- return activityName + " must *NOT* have been (re)created. mCreateCount="
- + lifecycleCounts.mCreateCount;
- }
- if (lifecycleCounts.mConfigurationChangedCount < 1) {
- return activityName + " must have received configuration changed. "
- + "mConfigurationChangedCount="
- + lifecycleCounts.mConfigurationChangedCount;
- }
- }
- return null;
- }
-
- protected void assertRelaunchOrConfigChanged(
- String activityName, int numRelaunch, int numConfigChange, String logSeparator)
- throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = verifyRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
- logSeparator);
- if (resultString != null) {
- log("***Waiting for relaunch or config changed: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- /** @return Error string if lifecycle counts don't match, null if everything is fine. */
- private String verifyRelaunchOrConfigChanged(String activityName, int numRelaunch,
- int numConfigChange, String logSeparator) throws DeviceNotAvailableException {
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
- logSeparator);
-
- if (lifecycleCounts.mDestroyCount != numRelaunch) {
- return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
- + " time(s), expecting " + numRelaunch;
- } else if (lifecycleCounts.mCreateCount != numRelaunch) {
- return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
- + " time(s), expecting " + numRelaunch;
- } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
- return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
- + " onConfigurationChanged() calls, expecting " + numConfigChange;
- }
- return null;
- }
-
- protected void assertActivityDestroyed(String activityName, String logSeparator)
- throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = verifyActivityDestroyed(activityName, logSeparator);
- if (resultString != null) {
- log("***Waiting for activity destroyed: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- /** @return Error string if lifecycle counts don't match, null if everything is fine. */
- private String verifyActivityDestroyed(String activityName, String logSeparator)
- throws DeviceNotAvailableException {
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
- logSeparator);
-
- if (lifecycleCounts.mDestroyCount != 1) {
- return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
- + " time(s), expecting single destruction.";
- } else if (lifecycleCounts.mCreateCount != 0) {
- return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
- + " time(s), not expecting any.";
- } else if (lifecycleCounts.mConfigurationChangedCount != 0) {
- return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
- + " onConfigurationChanged() calls, not expecting any.";
- }
- return null;
- }
-
- protected String[] getDeviceLogsForComponent(String componentName, String logSeparator)
- throws DeviceNotAvailableException {
- return getDeviceLogsForComponents(new String[]{componentName}, logSeparator);
- }
-
- protected String[] getDeviceLogsForComponents(final String[] componentNames,
- String logSeparator) throws DeviceNotAvailableException {
- String filters = LOG_SEPARATOR + ":I ";
- for (String component : componentNames) {
- filters += component + ":I ";
- }
- final String[] result = mDevice.executeAdbCommand(
- "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
- if (logSeparator == null) {
- return result;
- }
-
- // Make sure that we only check logs after the separator.
- int i = 0;
- boolean lookingForSeparator = true;
- while (i < result.length && lookingForSeparator) {
- if (result[i].contains(logSeparator)) {
- lookingForSeparator = false;
- }
- i++;
- }
- final String[] filteredResult = new String[result.length - i];
- for (int curPos = 0; i < result.length; curPos++, i++) {
- filteredResult[curPos] = result[i];
- }
- return filteredResult;
- }
-
- void assertSingleLaunch(String activityName, String logSeparator) throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = validateLifecycleCounts(activityName, logSeparator, 1 /* createCount */,
- 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
- 0 /* destroyCount */);
- if (resultString != null) {
- log("***Waiting for valid lifecycle state: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- public void assertSingleLaunchAndStop(String activityName, String logSeparator) throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = validateLifecycleCounts(activityName, logSeparator, 1 /* createCount */,
- 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
- 0 /* destroyCount */);
- if (resultString != null) {
- log("***Waiting for valid lifecycle state: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- public void assertSingleStartAndStop(String activityName, String logSeparator) throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = validateLifecycleCounts(activityName, logSeparator, 0 /* createCount */,
- 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
- 0 /* destroyCount */);
- if (resultString != null) {
- log("***Waiting for valid lifecycle state: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- void assertSingleStart(String activityName, String logSeparator) throws DeviceNotAvailableException {
- int retriesLeft = 5;
- String resultString;
- do {
- resultString = validateLifecycleCounts(activityName, logSeparator, 0 /* createCount */,
- 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
- 0 /* destroyCount */);
- if (resultString != null) {
- log("***Waiting for valid lifecycle state: " + resultString);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
-
- assertNull(resultString, resultString);
- }
-
- private String validateLifecycleCounts(String activityName, String logSeparator,
- int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
- int destroyCount) throws DeviceNotAvailableException {
-
- final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
- logSeparator);
-
- if (lifecycleCounts.mCreateCount != createCount) {
- return activityName + " created " + lifecycleCounts.mCreateCount + " times.";
- }
- if (lifecycleCounts.mStartCount != startCount) {
- return activityName + " started " + lifecycleCounts.mStartCount + " times.";
- }
- if (lifecycleCounts.mResumeCount != resumeCount) {
- return activityName + " resumed " + lifecycleCounts.mResumeCount + " times.";
- }
- if (lifecycleCounts.mPauseCount != pauseCount) {
- return activityName + " paused " + lifecycleCounts.mPauseCount + " times.";
- }
- if (lifecycleCounts.mStopCount != stopCount) {
- return activityName + " stopped " + lifecycleCounts.mStopCount + " times.";
- }
- if (lifecycleCounts.mDestroyCount != destroyCount) {
- return activityName + " destroyed " + lifecycleCounts.mDestroyCount + " times.";
- }
- return null;
- }
-
- private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
- private static final Pattern sStartPattern = Pattern.compile("(.+): onStart");
- private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
- private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
- private static final Pattern sConfigurationChangedPattern =
- Pattern.compile("(.+): onConfigurationChanged");
- private static final Pattern sMovedToDisplayPattern =
- Pattern.compile("(.+): onMovedToDisplay");
- private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
- private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
- private static final Pattern sMultiWindowModeChangedPattern =
- Pattern.compile("(.+): onMultiWindowModeChanged");
- private static final Pattern sPictureInPictureModeChangedPattern =
- Pattern.compile("(.+): onPictureInPictureModeChanged");
- private static final Pattern sNewConfigPattern = Pattern.compile(
- "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
- + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
- + " orientation=(\\d+)");
- private static final Pattern sDisplayStatePattern =
- Pattern.compile("Display Power: state=(.+)");
- private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
- private static final Pattern sUiModeLockedPattern =
- Pattern.compile("mUiModeLocked=(true|false)");
-
- class ReportedSizes {
- int widthDp;
- int heightDp;
- int displayWidth;
- int displayHeight;
- int metricsWidth;
- int metricsHeight;
- int smallestWidthDp;
- int densityDpi;
- int orientation;
-
- @Override
- public String toString() {
- return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
- + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
- + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
- + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
- + " orientation=" + orientation + "}";
- }
-
- @Override
- public boolean equals(Object obj) {
- if ( this == obj ) return true;
- if ( !(obj instanceof ReportedSizes) ) return false;
- ReportedSizes that = (ReportedSizes) obj;
- return widthDp == that.widthDp
- && heightDp == that.heightDp
- && displayWidth == that.displayWidth
- && displayHeight == that.displayHeight
- && metricsWidth == that.metricsWidth
- && metricsHeight == that.metricsHeight
- && smallestWidthDp == that.smallestWidthDp
- && densityDpi == that.densityDpi
- && orientation == that.orientation;
- }
- }
-
- ReportedSizes getLastReportedSizesForActivity(String activityName, String logSeparator)
- throws DeviceNotAvailableException {
- int retriesLeft = 5;
- ReportedSizes result;
- do {
- result = readLastReportedSizes(activityName, logSeparator);
- if (result == null) {
- log("***Waiting for sizes to be reported...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- } else {
- break;
- }
- } while (retriesLeft-- > 0);
- return result;
- }
-
- private ReportedSizes readLastReportedSizes(String activityName, String logSeparator)
- throws DeviceNotAvailableException {
- final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
- for (int i = lines.length - 1; i >= 0; i--) {
- final String line = lines[i].trim();
- final Matcher matcher = sNewConfigPattern.matcher(line);
- if (matcher.matches()) {
- ReportedSizes details = new ReportedSizes();
- details.widthDp = Integer.parseInt(matcher.group(2));
- details.heightDp = Integer.parseInt(matcher.group(3));
- details.displayWidth = Integer.parseInt(matcher.group(4));
- details.displayHeight = Integer.parseInt(matcher.group(5));
- details.metricsWidth = Integer.parseInt(matcher.group(6));
- details.metricsHeight = Integer.parseInt(matcher.group(7));
- details.smallestWidthDp = Integer.parseInt(matcher.group(8));
- details.densityDpi = Integer.parseInt(matcher.group(9));
- details.orientation = Integer.parseInt(matcher.group(10));
- return details;
- }
- }
- return null;
- }
-
- class ActivityLifecycleCounts {
- int mCreateCount;
- int mStartCount;
- int mResumeCount;
- int mConfigurationChangedCount;
- int mLastConfigurationChangedLineIndex;
- int mMovedToDisplayCount;
- int mMultiWindowModeChangedCount;
- int mLastMultiWindowModeChangedLineIndex;
- int mPictureInPictureModeChangedCount;
- int mLastPictureInPictureModeChangedLineIndex;
- int mPauseCount;
- int mStopCount;
- int mLastStopLineIndex;
- int mDestroyCount;
-
- public ActivityLifecycleCounts(String activityName, String logSeparator)
- throws DeviceNotAvailableException {
- int lineIndex = 0;
- for (String line : getDeviceLogsForComponent(activityName, logSeparator)) {
- line = line.trim();
- lineIndex++;
-
- Matcher matcher = sCreatePattern.matcher(line);
- if (matcher.matches()) {
- mCreateCount++;
- continue;
- }
-
- matcher = sStartPattern.matcher(line);
- if (matcher.matches()) {
- mStartCount++;
- continue;
- }
-
- matcher = sResumePattern.matcher(line);
- if (matcher.matches()) {
- mResumeCount++;
- continue;
- }
-
- matcher = sConfigurationChangedPattern.matcher(line);
- if (matcher.matches()) {
- mConfigurationChangedCount++;
- mLastConfigurationChangedLineIndex = lineIndex;
- continue;
- }
-
- matcher = sMovedToDisplayPattern.matcher(line);
- if (matcher.matches()) {
- mMovedToDisplayCount++;
- continue;
- }
-
- matcher = sMultiWindowModeChangedPattern.matcher(line);
- if (matcher.matches()) {
- mMultiWindowModeChangedCount++;
- mLastMultiWindowModeChangedLineIndex = lineIndex;
- continue;
- }
-
- matcher = sPictureInPictureModeChangedPattern.matcher(line);
- if (matcher.matches()) {
- mPictureInPictureModeChangedCount++;
- mLastPictureInPictureModeChangedLineIndex = lineIndex;
- continue;
- }
-
- matcher = sPausePattern.matcher(line);
- if (matcher.matches()) {
- mPauseCount++;
- continue;
- }
-
- matcher = sStopPattern.matcher(line);
- if (matcher.matches()) {
- mStopCount++;
- mLastStopLineIndex = lineIndex;
- continue;
- }
-
- matcher = sDestroyPattern.matcher(line);
- if (matcher.matches()) {
- mDestroyCount++;
- continue;
- }
- }
- }
- }
-
- protected void stopTestCase() throws Exception {
- executeShellCommand("am force-stop " + componentName);
- }
-
- protected LaunchActivityBuilder getLaunchActivityBuilder() {
- return new LaunchActivityBuilder(mAmWmState, mDevice);
- }
-
- protected static class LaunchActivityBuilder {
- private final ActivityAndWindowManagersState mAmWmState;
- private final ITestDevice mDevice;
-
- private String mTargetActivityName;
- private String mTargetPackage = componentName;
- private boolean mToSide;
- private boolean mRandomData;
- private boolean mNewTask;
- private boolean mMultipleTask;
- private int mDisplayId = INVALID_DISPLAY_ID;
- private String mLaunchingActivityName = LAUNCHING_ACTIVITY;
- private boolean mReorderToFront;
- private boolean mWaitForLaunched;
-
- public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState,
- ITestDevice device) {
- mAmWmState = amWmState;
- mDevice = device;
- mWaitForLaunched = true;
- }
-
- public LaunchActivityBuilder setToSide(boolean toSide) {
- mToSide = toSide;
- return this;
- }
-
- public LaunchActivityBuilder setRandomData(boolean randomData) {
- mRandomData = randomData;
- return this;
- }
-
- public LaunchActivityBuilder setNewTask(boolean newTask) {
- mNewTask = newTask;
- return this;
- }
-
- public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
- mMultipleTask = multipleTask;
- return this;
- }
-
- public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
- mReorderToFront = reorderToFront;
- return this;
- }
-
- public LaunchActivityBuilder setTargetActivityName(String name) {
- mTargetActivityName = name;
- return this;
- }
-
- public LaunchActivityBuilder setTargetPackage(String pkg) {
- mTargetPackage = pkg;
- return this;
- }
-
- public LaunchActivityBuilder setDisplayId(int id) {
- mDisplayId = id;
- return this;
- }
-
- public LaunchActivityBuilder setLaunchingActivityName(String name) {
- mLaunchingActivityName = name;
- return this;
- }
-
- public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
- mWaitForLaunched = shouldWait;
- return this;
- }
-
- public void execute() throws Exception {
- StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(mLaunchingActivityName));
- commandBuilder.append(" -f 0x20000000");
-
- // Add a flag to ensure we actually mean to launch an activity.
- commandBuilder.append(" --ez launch_activity true");
-
- if (mToSide) {
- commandBuilder.append(" --ez launch_to_the_side true");
- }
- if (mRandomData) {
- commandBuilder.append(" --ez random_data true");
- }
- if (mNewTask) {
- commandBuilder.append(" --ez new_task true");
- }
- if (mMultipleTask) {
- commandBuilder.append(" --ez multiple_task true");
- }
- if (mReorderToFront) {
- commandBuilder.append(" --ez reorder_to_front true");
- }
- if (mTargetActivityName != null) {
- commandBuilder.append(" --es target_activity ").append(mTargetActivityName);
- commandBuilder.append(" --es package_name ").append(mTargetPackage);
- }
- if (mDisplayId != INVALID_DISPLAY_ID) {
- commandBuilder.append(" --ei display_id ").append(mDisplayId);
- }
- executeShellCommand(mDevice, commandBuilder.toString());
-
- if (mWaitForLaunched) {
- mAmWmState.waitForValidState(mDevice, new String[]{mTargetActivityName},
- null /* stackIds */, false /* compareTaskAndStackBounds */, mTargetPackage);
- }
- }
- }
-
- void tearDownLockCredentials() throws Exception {
- if (!mLockCredentialsSet) {
- return;
- }
-
- removeLockCredential();
- // Dismiss active keyguard after credential is cleared, so
- // keyguard doesn't ask for the stale credential.
- pressBackButton();
- sleepDevice();
- wakeUpAndUnlockDevice();
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/StateLogger.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/StateLogger.java
deleted file mode 100644
index 335f26c..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/StateLogger.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.tradefed.log.LogUtil.CLog;
-
-import static com.android.ddmlib.Log.LogLevel.INFO;
-import static com.android.ddmlib.Log.LogLevel.ERROR;
-
-/**
- * Util class to perform simple state logging.
- */
-public class StateLogger {
- private static final boolean DEBUG = false;
-
- /** Simple info-level logging gated by {@link #DEBUG} flag */
- public static void log(String logText) {
- if (DEBUG) {
- CLog.logAndDisplay(INFO, logText);
- }
- }
-
- public static void logE(String logText) {
- CLog.logAndDisplay(ERROR, logText);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/SurfaceTraceReceiver.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/SurfaceTraceReceiver.java
deleted file mode 100644
index 8026e80..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/SurfaceTraceReceiver.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.IShellOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.awt.Rectangle;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.lang.System;
-import junit.framework.Assert;
-
-import static android.server.cts.StateLogger.logE;
-
-// Parses a trace of surface commands from the WM (in real time)
-// and dispenses them via the SurfaceObserver interface.
-//
-// Data enters through addOutput
-public class SurfaceTraceReceiver implements IShellOutputReceiver {
- final SurfaceObserver mObserver;
-
- private State mState = State.CMD;
- private String mCurrentWindowName = null;
- private int mArgPosition = 0;
- private float[] mTmpFloats = new float[10];
- private int[] mTmpInts = new int[10];
- private Rectangle.Float mTmpRect = new Rectangle.Float();
- private byte[] mUnprocessedBytes = new byte[16384];
- private byte[] mFullData = new byte[32768];
- private int mUnprocessedBytesLength;
-
- private boolean mCancelled = false;
-
- interface SurfaceObserver {
- default void setAlpha(String windowName, float alpha) {}
- default void setLayer(String windowName, int layer) {}
- default void setPosition(String windowName, float x, float y) {}
- default void setSize(String widnowName, int width, int height) {}
- default void setLayerStack(String windowName, int layerStack) {}
- default void setMatrix(String windowName, float dsdx, float dtdx, float dsdy, float dtdy) {}
- default void setCrop(String windowName, Rectangle.Float crop) {}
- default void setFinalCrop(String windowName, Rectangle.Float finalCrop) {}
- default void hide(String windowName) {}
- default void show(String windowName) {}
- default void setGeometryAppliesWithResize(String windowName) {}
- default void openTransaction() {}
- default void closeTransaction() {}
- };
-
- enum State {
- CMD,
- SET_ALPHA,
- SET_LAYER,
- SET_POSITION,
- SET_SIZE,
- SET_CROP,
- SET_FINAL_CROP,
- SET_LAYER_STACK,
- SET_MATRIX,
- HIDE,
- SHOW,
- GEOMETRY_APPLIES_WITH_RESIZE
- };
-
- SurfaceTraceReceiver(SurfaceObserver observer) {
- mObserver = observer;
- }
-
- // Reset state and prepare to accept a new command.
- void nextCmd(DataInputStream d) {
- mState = State.CMD;
- mCurrentWindowName = null;
- mArgPosition = 0;
-
- try {
- // Consume the sigil
- d.readByte();
- d.readByte();
- d.readByte();
- d.readByte();
- } catch (Exception e) {
- logE("Exception consuming sigil: " + e);
- }
- }
-
- // When the command parsing functions below are called, the window name
- // will already be parsed. The responsibility of these functions
- // is to parse other arguments 1 by 1 and accumlate them until the appropriate number
- // is reached. At that point the parser should emit an event to the observer and
- // call nextCmd
- void parseAlpha(DataInputStream d) throws IOException {
- float alpha = d.readFloat();
- mObserver.setAlpha(mCurrentWindowName, alpha);
- nextCmd(d);
- }
-
- void parseLayer(DataInputStream d) throws IOException {
- int layer = d.readInt();
- mObserver.setLayer(mCurrentWindowName, layer);
- nextCmd(d);
- }
-
- void parsePosition(DataInputStream d) throws IOException {
- mTmpFloats[mArgPosition] = d.readFloat();
- mArgPosition++;
- if (mArgPosition == 2) {
- mObserver.setPosition(mCurrentWindowName, mTmpFloats[0], mTmpFloats[1]);
- nextCmd(d);
- }
- }
-
- void parseSize(DataInputStream d) throws IOException {
- mTmpInts[mArgPosition] = d.readInt();
- mArgPosition++;
- if (mArgPosition == 2) {
- mObserver.setSize(mCurrentWindowName, mTmpInts[0], mTmpInts[1]);
- nextCmd(d);
- }
- }
-
- // Careful Android rectangle rep is top-left-right-bottom awt is top-left-width-height
- void parseCrop(DataInputStream d) throws IOException {
- mTmpFloats[mArgPosition] = d.readFloat();
- mArgPosition++;
- if (mArgPosition == 4) {
- mTmpRect.setRect(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2]-mTmpFloats[0],
- mTmpFloats[3]-mTmpFloats[1]);
- mObserver.setCrop(mCurrentWindowName, mTmpRect);
- nextCmd(d);
- }
- }
-
- void parseFinalCrop(DataInputStream d) throws IOException {
- mTmpFloats[mArgPosition] = d.readInt();
- mArgPosition++;
- if (mArgPosition == 4) {
- mTmpRect.setRect(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2]-mTmpFloats[0],
- mTmpFloats[3]-mTmpFloats[1]);
- mObserver.setFinalCrop(mCurrentWindowName, mTmpRect);
- nextCmd(d);
- }
- }
-
- void parseLayerStack(DataInputStream d) throws IOException {
- int layerStack = d.readInt();
- mObserver.setLayerStack(mCurrentWindowName, layerStack);
- nextCmd(d);
- }
-
- void parseSetMatrix(DataInputStream d) throws IOException {
- mTmpFloats[mArgPosition] = d.readFloat();
- mArgPosition++;
- if (mArgPosition == 4) {
- mObserver.setMatrix(mCurrentWindowName, mTmpFloats[0],
- mTmpFloats[1], mTmpFloats[2], mTmpFloats[3]);
- nextCmd(d);
- }
- }
-
- void parseHide(DataInputStream d) throws IOException {
- mObserver.hide(mCurrentWindowName);
- nextCmd(d);
- }
-
- void parseShow(DataInputStream d) throws IOException {
- mObserver.show(mCurrentWindowName);
- nextCmd(d);
- }
-
- void parseGeometryAppliesWithResize(DataInputStream d) throws IOException {
- mObserver.setGeometryAppliesWithResize(mCurrentWindowName);
- nextCmd(d);
- }
-
- public int indexAfterLastSigil(byte[] data, int offset, int length) {
- int idx = offset + length - 1;
- int sigilsNeeded = 4;
- byte sigil = (byte)0xfc;
- while (idx > offset) {
- if (data[idx] == sigil) {
- sigilsNeeded--;
- if (sigilsNeeded == 0) {
- return idx+4;
- }
- } else {
- sigilsNeeded = 4;
- }
- idx--;
- }
- return idx; // idx == offset at this point
- }
-
- // The tricky bit here is ADB may break up our words, and not send us complete messages,
- // or even complete integers! To ensure we process the data in appropciate chunks,
- // We look for a sigil (0xfcfcfcfc) and only process data when it ends in as igil.
- // Otherwise we save it and wait to receive a sigil, then process the merged data.
- public void addOutput(byte[] data, int offset, int length) {
- byte[] combinedData = data;
-
- // First we have to merge any unprocessed bytes from the last call in to
- // a combined array.
- if (mUnprocessedBytesLength > 0) {
- System.arraycopy(mUnprocessedBytes, 0, mFullData, 0, mUnprocessedBytesLength);
- System.arraycopy(data, offset, mFullData, mUnprocessedBytesLength, length);
- combinedData = mFullData;
- length = mUnprocessedBytesLength + length;
- offset = 0;
- mUnprocessedBytesLength = 0;
- }
-
- // Now we find the last sigil in our combined array. Everything before this index is
- // a properly terminated message ready to be parsed.
- int completedIndex = indexAfterLastSigil(combinedData, offset, length);
- // If there are any bytes left after the last sigil, save them for next time.
- if (completedIndex != length + offset) {
- mUnprocessedBytesLength = (length+offset)-(completedIndex);
- System.arraycopy(combinedData, completedIndex,
- mUnprocessedBytes, 0, mUnprocessedBytesLength);
- }
- // If there was no sigil, we have nothing to process yet.
- if (completedIndex <= offset) {
- return;
- }
- ByteArrayInputStream b = new ByteArrayInputStream(combinedData, offset, completedIndex - offset);
- DataInputStream d = new DataInputStream(b);
-
- // We may not receive an entire message at once (for example we may receive
- // a command without its arguments), so we track our current state, over multiple
- // addOutput calls. When we are in State.CMD it means we next expect a new command.
- // If we are not expecting a command, then all commands with arguments, begin with
- // a window name. Once we have the window name, individual parseAlpha,
- // parseLayer, etc...statements will parse command arguments one at a time. Once
- // the appropriate number of arguments is collected the observer will be invoked
- // and the state reset. For commands which have no arguments (e.g. open/close transaction),
- // parseCmd can emit the observer event and call nextCmd() right away.
- try {
- while (b.available() > 0) {
- if (mState != State.CMD && mCurrentWindowName == null) {
- mCurrentWindowName = d.readUTF();
- if (b.available() == 0) {
- return;
- }
- }
- switch (mState) {
- case CMD: {
- String cmd = d.readUTF();
- parseCmd(d, cmd);
- break;
- }
- case SET_ALPHA: {
- parseAlpha(d);
- break;
- }
- case SET_LAYER: {
- parseLayer(d);
- break;
- }
- case SET_POSITION: {
- parsePosition(d);
- break;
- }
- case SET_SIZE: {
- parseSize(d);
- break;
- }
- case SET_CROP: {
- parseCrop(d);
- break;
- }
- case SET_FINAL_CROP: {
- parseFinalCrop(d);
- break;
- }
- case SET_LAYER_STACK: {
- parseLayerStack(d);
- break;
- }
- case SET_MATRIX: {
- parseSetMatrix(d);
- break;
- }
- case HIDE: {
- parseHide(d);
- break;
- }
- case SHOW: {
- parseShow(d);
- break;
- }
- case GEOMETRY_APPLIES_WITH_RESIZE: {
- parseGeometryAppliesWithResize(d);
- break;
- }
- }
- }
- } catch (Exception e) {
- logE("Error in surface trace receiver: " + e.toString());
- }
- }
-
- void parseCmd(DataInputStream d, String cmd) {
- switch (cmd) {
- case "Alpha":
- mState = State.SET_ALPHA;
- break;
- case "Layer":
- mState = State.SET_LAYER;
- break;
- case "Position":
- mState = State.SET_POSITION;
- break;
- case "Size":
- mState = State.SET_SIZE;
- break;
- case "Crop":
- mState = State.SET_CROP;
- break;
- case "FinalCrop":
- mState = State.SET_FINAL_CROP;
- break;
- case "LayerStack":
- mState = State.SET_LAYER_STACK;
- break;
- case "Matrix":
- mState = State.SET_MATRIX;
- break;
- case "Hide":
- mState = State.HIDE;
- break;
- case "Show":
- mState = State.SHOW;
- break;
- case "GeometryAppliesWithResize":
- mState = State.GEOMETRY_APPLIES_WITH_RESIZE;
- break;
- case "OpenTransaction":
- mObserver.openTransaction();
- nextCmd(d);
- break;
- case "CloseTransaction":
- mObserver.closeTransaction();
- nextCmd(d);
- break;
- default:
- Assert.fail("Unexpected surface command: " + cmd);
- break;
- }
- }
-
- @Override
- public void flush() {
- }
-
- void cancel() {
- mCancelled = true;
- }
-
- @Override
- public boolean isCancelled() {
- return mCancelled;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
deleted file mode 100644
index c112a14..0000000
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/WindowManagerState.java
+++ /dev/null
@@ -1,1152 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
-import static android.server.cts.StateLogger.log;
-import static android.server.cts.StateLogger.logE;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class WindowManagerState {
-
- public static final String TRANSIT_ACTIVITY_OPEN = "TRANSIT_ACTIVITY_OPEN";
- public static final String TRANSIT_ACTIVITY_CLOSE = "TRANSIT_ACTIVITY_CLOSE";
- public static final String TRANSIT_TASK_OPEN = "TRANSIT_TASK_OPEN";
- public static final String TRANSIT_TASK_CLOSE = "TRANSIT_TASK_CLOSE";
-
- public static final String TRANSIT_WALLPAPER_OPEN = "TRANSIT_WALLPAPER_OPEN";
- public static final String TRANSIT_WALLPAPER_CLOSE = "TRANSIT_WALLPAPER_CLOSE";
- public static final String TRANSIT_WALLPAPER_INTRA_OPEN = "TRANSIT_WALLPAPER_INTRA_OPEN";
- public static final String TRANSIT_WALLPAPER_INTRA_CLOSE = "TRANSIT_WALLPAPER_INTRA_CLOSE";
-
- public static final String TRANSIT_KEYGUARD_GOING_AWAY = "TRANSIT_KEYGUARD_GOING_AWAY";
- public static final String TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER =
- "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
- public static final String TRANSIT_KEYGUARD_OCCLUDE = "TRANSIT_KEYGUARD_OCCLUDE";
- public static final String TRANSIT_KEYGUARD_UNOCCLUDE = "TRANSIT_KEYGUARD_UNOCCLUDE";
-
- public static final String APP_STATE_IDLE = "APP_STATE_IDLE";
-
- private static final String DUMPSYS_WINDOW = "dumpsys window -a";
-
- private static final Pattern sWindowPattern =
- Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+)\\}\\:");
- private static final Pattern sStartingWindowPattern =
- Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Starting (.+)\\}\\:");
- private static final Pattern sExitingWindowPattern =
- Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+) EXITING\\}\\:");
- private static final Pattern sDebuggerWindowPattern =
- Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Waiting For Debugger: (.+)\\}\\:");
-
- private static final Pattern sFocusedWindowPattern = Pattern.compile(
- "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}");
- private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile(
- "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}");
- private static final Pattern sWaitingForDebuggerFocusedWindowPattern = Pattern.compile(
- "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Waiting For Debugger\\: (\\S+)\\}");
-
- private static final Pattern sFocusedAppPattern =
- Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) "
- + "ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)");
- private static final Pattern sStableBoundsPattern = Pattern.compile(
- "mStable=\\((\\d+),(\\d+)\\)-\\((\\d+),(\\d+)\\)");
- private static final Pattern sDefaultPinnedStackBoundsPattern = Pattern.compile(
- "defaultBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
- private static final Pattern sPinnedStackMovementBoundsPattern = Pattern.compile(
- "movementBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
- private static final Pattern sRotationPattern = Pattern.compile(
- "mRotation=(\\d).*");
- private static final Pattern sLastOrientationPattern = Pattern.compile(
- ".*mLastOrientation=(\\d)");
-
- private static final Pattern sLastAppTransitionPattern =
- Pattern.compile("mLastUsedAppTransition=(.+)");
- private static final Pattern sAppTransitionStatePattern =
- Pattern.compile("mAppTransitionState=(.+)");
-
- private static final Pattern sStackIdPattern = Pattern.compile("mStackId=(\\d+)");
-
- private static final Pattern sInputMethodWindowPattern =
- Pattern.compile("mInputMethodWindow=Window\\{([0-9a-fA-F]+) u\\d+ .+\\}.*");
-
- private static final Pattern sDisplayIdPattern =
- Pattern.compile("Display: mDisplayId=(\\d+)");
-
- private static final Pattern sDisplayFrozenPattern =
- Pattern.compile("mDisplayFrozen=([a-z]*) .*");
-
- private static final Pattern sDockedStackMinimizedPattern =
- Pattern.compile("mMinimizedDock=([a-z]*)");
-
- private static final Pattern[] sExtractStackExitPatterns = {
- sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern,
- sDebuggerWindowPattern, sFocusedWindowPattern, sAppErrorFocusedWindowPattern,
- sWaitingForDebuggerFocusedWindowPattern,
- sFocusedAppPattern, sLastAppTransitionPattern, sDefaultPinnedStackBoundsPattern,
- sPinnedStackMovementBoundsPattern, sDisplayIdPattern, sDockedStackMinimizedPattern};
-
- // Windows in z-order with the top most at the front of the list.
- private List<WindowState> mWindowStates = new ArrayList();
- // Stacks in z-order with the top most at the front of the list, starting with primary display.
- private final List<WindowStack> mStacks = new ArrayList();
- // Stacks on all attached displays, in z-order with the top most at the front of the list.
- private final Map<Integer, List<WindowStack>> mDisplayStacks
- = new HashMap<>();
- private List<Display> mDisplays = new ArrayList();
- private String mFocusedWindow = null;
- private String mFocusedApp = null;
- private String mLastTransition = null;
- private String mAppTransitionState = null;
- private String mInputMethodWindowAppToken = null;
- private Rectangle mStableBounds = new Rectangle();
- private final Rectangle mDefaultPinnedStackBounds = new Rectangle();
- private final Rectangle mPinnedStackMovementBounds = new Rectangle();
- private final LinkedList<String> mSysDump = new LinkedList();
- private int mRotation;
- private int mLastOrientation;
- private boolean mDisplayFrozen;
- private boolean mIsDockedStackMinimized;
-
- void computeState(ITestDevice device) throws DeviceNotAvailableException {
- // It is possible the system is in the middle of transition to the right state when we get
- // the dump. We try a few times to get the information we need before giving up.
- int retriesLeft = 3;
- boolean retry = false;
- String dump = null;
-
- log("==============================");
- log(" WindowManagerState ");
- log("==============================");
- do {
- if (retry) {
- log("***Incomplete WM state. Retrying...");
- // Wait half a second between retries for window manager to finish transitioning...
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- log(e.toString());
- // Well I guess we are not waiting...
- }
- }
-
- final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- device.executeShellCommand(DUMPSYS_WINDOW, outputReceiver);
- dump = outputReceiver.getOutput();
- parseSysDump(dump);
-
- retry = mWindowStates.isEmpty() || mFocusedApp == null;
- } while (retry && retriesLeft-- > 0);
-
- if (retry) {
- log(dump);
- }
-
- if (mWindowStates.isEmpty()) {
- logE("No Windows found...");
- }
- if (mFocusedWindow == null) {
- logE("No Focused Window...");
- }
- if (mFocusedApp == null) {
- logE("No Focused App...");
- }
- }
-
- private void parseSysDump(String sysDump) {
- reset();
-
- Collections.addAll(mSysDump, sysDump.split("\\n"));
-
- int currentDisplayId = DEFAULT_DISPLAY_ID;
- while (!mSysDump.isEmpty()) {
- final Display display =
- Display.create(mSysDump, sExtractStackExitPatterns);
- if (display != null) {
- log(display.toString());
- mDisplays.add(display);
- currentDisplayId = display.mDisplayId;
- mDisplayStacks.put(currentDisplayId, new ArrayList<>());
- continue;
- }
-
- final WindowStack stack =
- WindowStack.create(mSysDump, sStackIdPattern, sExtractStackExitPatterns);
-
- if (stack != null) {
- mStacks.add(stack);
- mDisplayStacks.get(currentDisplayId).add(stack);
- continue;
- }
-
-
- final WindowState ws = WindowState.create(mSysDump, sExtractStackExitPatterns);
- if (ws != null) {
- log(ws.toString());
-
- // Check to see if we are in the middle of transitioning. If we are, we want to
- // skip dumping until window manager is done transitioning windows.
- if (ws.isStartingWindow()) {
- log("Skipping dump due to starting window transition...");
- return;
- }
-
- if (ws.isExitingWindow()) {
- log("Skipping dump due to exiting window transition...");
- return;
- }
-
- mWindowStates.add(ws);
- continue;
- }
-
- final String line = mSysDump.pop().trim();
-
- Matcher matcher = sFocusedWindowPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String focusedWindow = matcher.group(3);
- log(focusedWindow);
- mFocusedWindow = focusedWindow;
- continue;
- }
-
- matcher = sAppErrorFocusedWindowPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String focusedWindow = matcher.group(3);
- log(focusedWindow);
- mFocusedWindow = focusedWindow;
- continue;
- }
-
- matcher = sWaitingForDebuggerFocusedWindowPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String focusedWindow = matcher.group(3);
- log(focusedWindow);
- mFocusedWindow = focusedWindow;
- continue;
- }
-
- matcher = sFocusedAppPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String focusedApp = matcher.group(5);
- log(focusedApp);
- mFocusedApp = focusedApp;
- continue;
- }
-
- matcher = sAppTransitionStatePattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String appTransitionState = matcher.group(1);
- log(appTransitionState);
- mAppTransitionState = appTransitionState;
- continue;
- }
-
- matcher = sLastAppTransitionPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String lastAppTransitionPattern = matcher.group(1);
- log(lastAppTransitionPattern);
- mLastTransition = lastAppTransitionPattern;
- continue;
- }
-
- matcher = sStableBoundsPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- int left = Integer.parseInt(matcher.group(1));
- int top = Integer.parseInt(matcher.group(2));
- int right = Integer.parseInt(matcher.group(3));
- int bottom = Integer.parseInt(matcher.group(4));
- mStableBounds.setBounds(left, top, right - left, bottom - top);
- log(mStableBounds.toString());
- continue;
- }
-
- matcher = sDefaultPinnedStackBoundsPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- int left = Integer.parseInt(matcher.group(1));
- int top = Integer.parseInt(matcher.group(2));
- int right = Integer.parseInt(matcher.group(3));
- int bottom = Integer.parseInt(matcher.group(4));
- mDefaultPinnedStackBounds.setBounds(left, top, right - left, bottom - top);
- log(mDefaultPinnedStackBounds.toString());
- continue;
- }
-
- matcher = sPinnedStackMovementBoundsPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- int left = Integer.parseInt(matcher.group(1));
- int top = Integer.parseInt(matcher.group(2));
- int right = Integer.parseInt(matcher.group(3));
- int bottom = Integer.parseInt(matcher.group(4));
- mPinnedStackMovementBounds.setBounds(left, top, right - left, bottom - top);
- log(mPinnedStackMovementBounds.toString());
- continue;
- }
-
- matcher = sInputMethodWindowPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mInputMethodWindowAppToken = matcher.group(1);
- log(mInputMethodWindowAppToken);
- continue;
- }
-
- matcher = sRotationPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mRotation = Integer.parseInt(matcher.group(1));
- continue;
- }
-
- matcher = sLastOrientationPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mLastOrientation = Integer.parseInt(matcher.group(1));
- continue;
- }
-
- matcher = sDisplayFrozenPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mDisplayFrozen = Boolean.parseBoolean(matcher.group(1));
- continue;
- }
-
- matcher = sDockedStackMinimizedPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mIsDockedStackMinimized = Boolean.parseBoolean(matcher.group(1));
- continue;
- }
- }
- }
-
- void getMatchingWindowTokens(final String windowName, List<String> tokenList) {
- tokenList.clear();
-
- for (WindowState ws : mWindowStates) {
- if (windowName.equals(ws.getName())) {
- tokenList.add(ws.getToken());
- }
- }
- }
-
- void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
- windowList.clear();
- for (WindowState ws : mWindowStates) {
- if (ws.isShown() && windowName.equals(ws.getName())) {
- windowList.add(ws);
- }
- }
- }
-
- void getPrefixMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
- windowList.clear();
- for (WindowState ws : mWindowStates) {
- if (ws.isShown() && ws.getName().startsWith(windowName)) {
- windowList.add(ws);
- }
- }
- }
-
- WindowState getWindowByPackageName(String packageName, int windowType) {
- for (WindowState ws : mWindowStates) {
- final String name = ws.getName();
- if (name == null || !name.contains(packageName)) {
- continue;
- }
- if (windowType != ws.getType()) {
- continue;
- }
- return ws;
- }
-
- return null;
- }
-
- void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
- List<WindowState> outWindowList) {
- outWindowList.clear();
- for (WindowState ws : mWindowStates) {
- final String name = ws.getName();
- if (name == null || !name.contains(packageName)) {
- continue;
- }
- if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
- continue;
- }
- outWindowList.add(ws);
- }
- }
-
- void sortWindowsByLayer(List<WindowState> windows) {
- windows.sort(Comparator.comparingInt(WindowState::getLayer));
- }
-
- WindowState getWindowStateForAppToken(String appToken) {
- for (WindowState ws : mWindowStates) {
- if (ws.getToken().equals(appToken)) {
- return ws;
- }
- }
- return null;
- }
-
- Display getDisplay(int displayId) {
- for (Display display : mDisplays) {
- if (displayId == display.getDisplayId()) {
- return display;
- }
- }
- return null;
- }
-
- String getFrontWindow() {
- if (mWindowStates == null || mWindowStates.isEmpty()) {
- return null;
- }
- return mWindowStates.get(0).getName();
- }
-
- String getFocusedWindow() {
- return mFocusedWindow;
- }
-
- String getFocusedApp() {
- return mFocusedApp;
- }
-
- String getLastTransition() {
- return mLastTransition;
- }
-
- String getAppTransitionState() {
- return mAppTransitionState;
- }
-
- int getFrontStackId(int displayId) {
- return mDisplayStacks.get(displayId).get(0).mStackId;
- }
-
- public int getRotation() {
- return mRotation;
- }
-
- int getLastOrientation() {
- return mLastOrientation;
- }
-
- boolean containsStack(int stackId) {
- for (WindowStack stack : mStacks) {
- if (stackId == stack.mStackId) {
- return true;
- }
- }
- return false;
- }
-
- /** Check if there exists a window record with matching windowName. */
- boolean containsWindow(String windowName) {
- for (WindowState window : mWindowStates) {
- if (window.getName().equals(windowName)) {
- return true;
- }
- }
- return false;
- }
-
- /** Check if at least one window which matches provided window name is visible. */
- boolean isWindowVisible(String windowName) {
- for (WindowState window : mWindowStates) {
- if (window.getName().equals(windowName)) {
- if (window.isShown()) {
- return true;
- }
- }
- }
- return false;
- }
-
- boolean allWindowsVisible(String windowName) {
- boolean allVisible = false;
- for (WindowState window : mWindowStates) {
- if (window.getName().equals(windowName)) {
- if (!window.isShown()) {
- log("[VISIBLE] not visible" + windowName);
- return false;
- }
- log("[VISIBLE] visible" + windowName);
- allVisible = true;
- }
- }
- return allVisible;
- }
-
- WindowStack getStack(int stackId) {
- for (WindowStack stack : mStacks) {
- if (stackId == stack.mStackId) {
- return stack;
- }
- }
- return null;
- }
-
-
- int getStackPosition(int stackId) {
- for (Integer displayId : mDisplayStacks.keySet()) {
- List<WindowStack> stacks = mDisplayStacks.get(displayId);
- for (int i = 0; i < stacks.size(); i++) {
- if (stackId == stacks.get(i).mStackId) {
- return i;
- }
- }
- }
- return -1;
- }
-
- WindowState getInputMethodWindowState() {
- return getWindowStateForAppToken(mInputMethodWindowAppToken);
- }
-
- Rectangle getStableBounds() {
- return mStableBounds;
- }
-
- Rectangle getDefaultPinnedStackBounds() {
- return mDefaultPinnedStackBounds;
- }
-
- Rectangle getPinnedStackMomentBounds() {
- return mPinnedStackMovementBounds;
- }
-
- WindowState findFirstWindowWithType(int type) {
- for (WindowState window : mWindowStates) {
- if (window.getType() == type) {
- return window;
- }
- }
- return null;
- }
-
- public boolean isDisplayFrozen() {
- return mDisplayFrozen;
- }
-
- public boolean isDockedStackMinimized() {
- return mIsDockedStackMinimized;
- }
-
- private void reset() {
- mSysDump.clear();
- mStacks.clear();
- mDisplays.clear();
- mWindowStates.clear();
- mFocusedWindow = null;
- mFocusedApp = null;
- mInputMethodWindowAppToken = null;
- }
-
- static class WindowStack extends WindowContainer {
-
- private static final Pattern sTaskIdPattern = Pattern.compile("taskId=(\\d+)");
- private static final Pattern sWindowAnimationBackgroundSurfacePattern =
- Pattern.compile("mWindowAnimationBackgroundSurface:");
-
- int mStackId;
- ArrayList<WindowTask> mTasks = new ArrayList();
- boolean mWindowAnimationBackgroundSurfaceShowing;
-
- private WindowStack() {
-
- }
-
- static WindowStack create(
- LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns) {
- final String line = dump.peek().trim();
-
- final Matcher matcher = stackIdPattern.matcher(line);
- if (!matcher.matches()) {
- // Not a stack.
- return null;
- }
- // For the stack Id line we just read.
- dump.pop();
-
- final WindowStack stack = new WindowStack();
- log(line);
- final String stackId = matcher.group(1);
- log(stackId);
- stack.mStackId = Integer.parseInt(stackId);
- stack.extract(dump, exitPatterns);
- return stack;
- }
-
- void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
-
- final List<Pattern> taskExitPatterns = new ArrayList();
- Collections.addAll(taskExitPatterns, exitPatterns);
- taskExitPatterns.add(sTaskIdPattern);
- taskExitPatterns.add(sWindowAnimationBackgroundSurfacePattern);
- final Pattern[] taskExitPatternsArray =
- taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
-
- while (!doneExtracting(dump, exitPatterns)) {
- final WindowTask task =
- WindowTask.create(dump, sTaskIdPattern, taskExitPatternsArray);
-
- if (task != null) {
- mTasks.add(task);
- continue;
- }
-
- final String line = dump.pop().trim();
-
- if (extractFullscreen(line)) {
- continue;
- }
-
- if (extractBounds(line)) {
- continue;
- }
-
- if (extractWindowAnimationBackgroundSurface(line)) {
- continue;
- }
- }
- }
-
- boolean extractWindowAnimationBackgroundSurface(String line) {
- if (sWindowAnimationBackgroundSurfacePattern.matcher(line).matches()) {
- log(line);
- mWindowAnimationBackgroundSurfaceShowing = true;
- return true;
- }
- return false;
- }
-
- WindowTask getTask(int taskId) {
- for (WindowTask task : mTasks) {
- if (taskId == task.mTaskId) {
- return task;
- }
- }
- return null;
- }
-
- boolean isWindowAnimationBackgroundSurfaceShowing() {
- return mWindowAnimationBackgroundSurfaceShowing;
- }
- }
-
- static class WindowTask extends WindowContainer {
- private static final Pattern sTempInsetBoundsPattern =
- Pattern.compile("mTempInsetBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
-
- private static final Pattern sAppTokenPattern = Pattern.compile(
- "Activity #(\\d+) AppWindowToken\\{(\\S+) token=Token\\{(\\S+) "
- + "ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}\\}\\}");
-
-
- int mTaskId;
- Rectangle mTempInsetBounds;
- List<String> mAppTokens = new ArrayList();
-
- private WindowTask() {
- }
-
- static WindowTask create(
- LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) {
- final String line = dump.peek().trim();
-
- final Matcher matcher = taskIdPattern.matcher(line);
- if (!matcher.matches()) {
- // Not a task.
- return null;
- }
- // For the task Id line we just read.
- dump.pop();
-
- final WindowTask task = new WindowTask();
- log(line);
- final String taskId = matcher.group(1);
- log(taskId);
- task.mTaskId = Integer.parseInt(taskId);
- task.extract(dump, exitPatterns);
- return task;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
- while (!doneExtracting(dump, exitPatterns)) {
- final String line = dump.pop().trim();
-
- if (extractFullscreen(line)) {
- continue;
- }
-
- if (extractBounds(line)) {
- continue;
- }
-
- Matcher matcher = sTempInsetBoundsPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- mTempInsetBounds = extractBounds(matcher);
- }
-
- matcher = sAppTokenPattern.matcher(line);
- if (matcher.matches()) {
- log(line);
- final String appToken = matcher.group(6);
- log(appToken);
- mAppTokens.add(appToken);
- continue;
- }
- }
- }
- }
-
- static abstract class WindowContainer {
- protected static final Pattern sFullscreenPattern = Pattern.compile("mFillsParent=(\\S+)");
- protected static final Pattern sBoundsPattern =
- Pattern.compile("mBounds=\\[(-?\\d+),(-?\\d+)\\]\\[(-?\\d+),(-?\\d+)\\]");
-
- protected boolean mFullscreen;
- protected Rectangle mBounds;
-
- static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) {
- if (dump.isEmpty()) {
- return true;
- }
- final String line = dump.peek().trim();
-
- for (Pattern pattern : exitPatterns) {
- if (pattern.matcher(line).matches()) {
- return true;
- }
- }
- return false;
- }
-
- boolean extractFullscreen(String line) {
- final Matcher matcher = sFullscreenPattern.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- log(line);
- final String fullscreen = matcher.group(1);
- log(fullscreen);
- mFullscreen = Boolean.valueOf(fullscreen);
- return true;
- }
-
- boolean extractBounds(String line) {
- final Matcher matcher = sBoundsPattern.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- log(line);
- mBounds = extractBounds(matcher);
- return true;
- }
-
- static Rectangle extractBounds(Matcher matcher) {
- final int left = Integer.valueOf(matcher.group(1));
- final int top = Integer.valueOf(matcher.group(2));
- final int right = Integer.valueOf(matcher.group(3));
- final int bottom = Integer.valueOf(matcher.group(4));
- final Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
-
- log(rect.toString());
- return rect;
- }
-
- static void extractMultipleBounds(Matcher matcher, int groupIndex, Rectangle... rectList) {
- for (Rectangle rect : rectList) {
- if (rect == null) {
- return;
- }
- final int left = Integer.valueOf(matcher.group(groupIndex++));
- final int top = Integer.valueOf(matcher.group(groupIndex++));
- final int right = Integer.valueOf(matcher.group(groupIndex++));
- final int bottom = Integer.valueOf(matcher.group(groupIndex++));
- rect.setBounds(left, top, right - left, bottom - top);
- }
- }
-
- Rectangle getBounds() {
- return mBounds;
- }
-
- boolean isFullscreen() {
- return mFullscreen;
- }
- }
-
- static class Display extends WindowContainer {
- private static final String TAG = "[Display] ";
-
- private static final Pattern sDisplayInfoPattern =
- Pattern.compile("(.+) (\\d+)dpi cur=(\\d+)x(\\d+) app=(\\d+)x(\\d+) (.+)");
-
- private final int mDisplayId;
- private Rectangle mDisplayRect = new Rectangle();
- private Rectangle mAppRect = new Rectangle();
- private int mDpi;
-
- private Display(int displayId) {
- mDisplayId = displayId;
- }
-
- int getDisplayId() {
- return mDisplayId;
- }
-
- int getDpi() {
- return mDpi;
- }
-
- Rectangle getDisplayRect() {
- return mDisplayRect;
- }
-
- Rectangle getAppRect() {
- return mAppRect;
- }
-
- static Display create(LinkedList<String> dump, Pattern[] exitPatterns) {
- // TODO: exit pattern for displays?
- final String line = dump.peek().trim();
-
- Matcher matcher = sDisplayIdPattern.matcher(line);
- if (!matcher.matches()) {
- return null;
- }
-
- log(TAG + "DISPLAY_ID: " + line);
- dump.pop();
-
- final int displayId = Integer.valueOf(matcher.group(1));
- final Display display = new Display(displayId);
- display.extract(dump, exitPatterns);
- return display;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
- while (!doneExtracting(dump, exitPatterns)) {
- final String line = dump.pop().trim();
-
- final Matcher matcher = sDisplayInfoPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "DISPLAY_INFO: " + line);
- mDpi = Integer.valueOf(matcher.group(2));
-
- final int displayWidth = Integer.valueOf(matcher.group(3));
- final int displayHeight = Integer.valueOf(matcher.group(4));
- mDisplayRect.setBounds(0, 0, displayWidth, displayHeight);
-
- final int appWidth = Integer.valueOf(matcher.group(5));
- final int appHeight = Integer.valueOf(matcher.group(6));
- mAppRect.setBounds(0, 0, appWidth, appHeight);
-
- // break as we don't need other info for now
- break;
- }
- // Extract other info here if needed
- }
- }
-
- @Override
- public String toString() {
- return "Display #" + mDisplayId + ": mDisplayRect=" + mDisplayRect
- + " mAppRect=" + mAppRect;
- }
- }
-
- public static class WindowState extends WindowContainer {
- private static final String TAG = "[WindowState] ";
-
- public static final int TYPE_WALLPAPER = 2013;
-
- private static final int WINDOW_TYPE_NORMAL = 0;
- private static final int WINDOW_TYPE_STARTING = 1;
- private static final int WINDOW_TYPE_EXITING = 2;
- private static final int WINDOW_TYPE_DEBUGGER = 3;
-
- private static final String RECT_STR = "\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]";
- private static final String NEGATIVE_VALUES_ALLOWED_RECT_STR =
- "\\[([-\\d]+),([-\\d]+)\\]\\[([-\\d]+),([-\\d]+)\\]";
- private static final Pattern sMainFramePattern = Pattern.compile("mFrame=" + RECT_STR + ".+");
- private static final Pattern sFramePattern =
- Pattern.compile("Frames: containing=" + RECT_STR + " parent=" + RECT_STR);
- private static final Pattern sContentFramePattern =
- Pattern.compile("content=" + RECT_STR + " .+");
- private static final Pattern sWindowAssociationPattern =
- Pattern.compile("mDisplayId=(\\d+) stackId=(\\d+) (.+)");
- private static final Pattern sSurfaceInsetsPattern =
- Pattern.compile("Cur insets.+surface=" + RECT_STR + ".+");
- private static final Pattern sContentInsetsPattern =
- Pattern.compile("Cur insets.+content=" + NEGATIVE_VALUES_ALLOWED_RECT_STR + ".+");
- private static final Pattern sGivenContentInsetsPattern =
- Pattern.compile("mGivenContentInsets=" + RECT_STR + ".+");
- private static final Pattern sCropPattern =
- Pattern.compile(".+mLastClipRect=" + RECT_STR + ".*");
- private static final Pattern sSurfacePattern =
- Pattern.compile("Surface: shown=(\\S+) layer=(\\d+) alpha=[\\d.]+ rect=\\([\\d.-]+,[\\d.-]+\\) [\\d.]+ x [\\d.]+.*");
- private static final Pattern sAttrsPattern=
- Pattern.compile("mAttrs=WM\\.LayoutParams\\{.*ty=(\\d+).*\\}");
-
-
- private final String mName;
- private final String mAppToken;
- private final int mWindowType;
- private int mType;
- private int mDisplayId;
- private int mStackId;
- private int mLayer;
- private boolean mShown;
- private Rectangle mContainingFrame = new Rectangle();
- private Rectangle mParentFrame = new Rectangle();
- private Rectangle mContentFrame = new Rectangle();
- private Rectangle mFrame = new Rectangle();
- private Rectangle mSurfaceInsets = new Rectangle();
- private Rectangle mContentInsets = new Rectangle();
- private Rectangle mGivenContentInsets = new Rectangle();
- private Rectangle mCrop = new Rectangle();
-
-
- private WindowState(Matcher matcher, int windowType) {
- mName = matcher.group(4);
- mAppToken = matcher.group(2);
- mWindowType = windowType;
- }
-
- public String getName() {
- return mName;
- }
-
- String getToken() {
- return mAppToken;
- }
-
- boolean isStartingWindow() {
- return mWindowType == WINDOW_TYPE_STARTING;
- }
-
- boolean isExitingWindow() {
- return mWindowType == WINDOW_TYPE_EXITING;
- }
-
- boolean isDebuggerWindow() {
- return mWindowType == WINDOW_TYPE_DEBUGGER;
- }
-
- int getDisplayId() {
- return mDisplayId;
- }
-
- int getStackId() {
- return mStackId;
- }
-
- int getLayer() {
- return mLayer;
- }
-
- Rectangle getContainingFrame() {
- return mContainingFrame;
- }
-
- Rectangle getFrame() {
- return mFrame;
- }
-
- Rectangle getSurfaceInsets() {
- return mSurfaceInsets;
- }
-
- Rectangle getContentInsets() {
- return mContentInsets;
- }
-
- Rectangle getGivenContentInsets() {
- return mGivenContentInsets;
- }
-
- Rectangle getContentFrame() {
- return mContentFrame;
- }
-
- Rectangle getParentFrame() {
- return mParentFrame;
- }
-
- Rectangle getCrop() {
- return mCrop;
- }
-
- boolean isShown() {
- return mShown;
- }
-
- int getType() {
- return mType;
- }
-
- static WindowState create(LinkedList<String> dump, Pattern[] exitPatterns) {
- final String line = dump.peek().trim();
-
- Matcher matcher = sWindowPattern.matcher(line);
- if (!matcher.matches()) {
- return null;
- }
-
- log(TAG + "WINDOW: " + line);
- dump.pop();
-
- final WindowState window;
- Matcher specialMatcher;
- if ((specialMatcher = sStartingWindowPattern.matcher(line)).matches()) {
- log(TAG + "STARTING: " + line);
- window = new WindowState(specialMatcher, WINDOW_TYPE_STARTING);
- } else if ((specialMatcher = sExitingWindowPattern.matcher(line)).matches()) {
- log(TAG + "EXITING: " + line);
- window = new WindowState(specialMatcher, WINDOW_TYPE_EXITING);
- } else if ((specialMatcher = sDebuggerWindowPattern.matcher(line)).matches()) {
- log(TAG + "DEBUGGER: " + line);
- window = new WindowState(specialMatcher, WINDOW_TYPE_DEBUGGER);
- } else {
- window = new WindowState(matcher, WINDOW_TYPE_NORMAL);
- }
-
- window.extract(dump, exitPatterns);
- return window;
- }
-
- private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
- while (!doneExtracting(dump, exitPatterns)) {
- final String line = dump.pop().trim();
-
- Matcher matcher = sWindowAssociationPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "WINDOW_ASSOCIATION: " + line);
- mDisplayId = Integer.valueOf(matcher.group(1));
- mStackId = Integer.valueOf(matcher.group(2));
- continue;
- }
-
- matcher = sMainFramePattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "MAIN WINDOW FRAME: " + line);
- mFrame = extractBounds(matcher);
- continue;
- }
-
- matcher = sFramePattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "FRAME: " + line);
- extractMultipleBounds(matcher, 1, mContainingFrame, mParentFrame);
- continue;
- }
-
- matcher = sContentFramePattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "CONTENT FRAME: " + line);
- mContentFrame = extractBounds(matcher);
- }
-
- matcher = sSurfaceInsetsPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "INSETS: " + line);
- mSurfaceInsets = extractBounds(matcher);
- }
-
- matcher = sContentInsetsPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "CONTENT INSETS: " + line);
- mContentInsets = extractBounds(matcher);
- }
-
- matcher = sCropPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "CROP: " + line);
- mCrop = extractBounds(matcher);
- }
-
- matcher = sSurfacePattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "SURFACE: " + line);
- mShown = Boolean.valueOf(matcher.group(1));
- mLayer = Integer.valueOf(matcher.group(2));
- }
-
- matcher = sAttrsPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "ATTRS: " + line);
- mType = Integer.valueOf(matcher.group(1));
- }
-
- matcher = sGivenContentInsetsPattern.matcher(line);
- if (matcher.matches()) {
- log(TAG + "GIVEN CONTENT INSETS: " + line);
- mGivenContentInsets = extractBounds(matcher);
- }
-
- // Extract other info here if needed
- }
- }
-
- private static String getWindowTypeSuffix(int windowType) {
- switch (windowType) {
- case WINDOW_TYPE_STARTING: return " STARTING";
- case WINDOW_TYPE_EXITING: return " EXITING";
- case WINDOW_TYPE_DEBUGGER: return " DEBUGGER";
- default: break;
- }
- return "";
- }
-
- @Override
- public String toString() {
- return "WindowState: {" + mAppToken + " " + mName
- + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
- + " cf=" + mContainingFrame + " pf=" + mParentFrame;
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/Android.mk
deleted file mode 100644
index 0739743..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/Android.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_MODULE := CtsWindowManagerHostTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed CtsServicesHostTestCases
-LOCAL_STATIC_JAVA_LIBRARIES := \
- cts-amwm-util \
- platform-test-annotations-host
-
-LOCAL_CTS_TEST_PACKAGE := android.server.cts
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_HOST_JAVA_LIBRARY)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
deleted file mode 100644
index 82f0099..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Config for CTS window manager host test cases">
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsDragAndDropSourceApp.apk" />
- <option name="test-file-name" value="CtsDragAndDropTargetApp.apk" />
- <option name="test-file-name" value="CtsDragAndDropTargetAppSdk23.apk" />
- <option name="test-file-name" value="CtsDeviceWindowFramesTestApp.apk" />
- <option name="test-file-name" value="CtsDeviceAlertWindowTestApp.apk" />
- <option name="test-file-name" value="CtsDeviceAlertWindowTestAppSdk25.apk" />
- </target_preparer>
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="CtsWindowManagerHostTestCases.jar" />
- <option name="runtime-hint" value="20m40s" />
- </test>
-</configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
deleted file mode 100755
index 9c6a6ad..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.server.alertwindowapp">
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
- <application android:label="CtsAlertWindow">
- <activity android:name=".AlertWindowTestActivity"
- android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
deleted file mode 100644
index db007e2..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/src/android/server/alertwindowapp/AlertWindowTestActivity.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.alertwindowapp;
-
-import android.os.Bundle;
-import android.server.alertwindowappsdk25.AlertWindowTestBaseActivity;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-
-public class AlertWindowTestActivity extends AlertWindowTestBaseActivity {
- private static final int[] ALERT_WINDOW_TYPES = {
- TYPE_PHONE,
- TYPE_PRIORITY_PHONE,
- TYPE_SYSTEM_ALERT,
- TYPE_SYSTEM_ERROR,
- TYPE_SYSTEM_OVERLAY,
- TYPE_APPLICATION_OVERLAY
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- createAllAlertWindows(getPackageName());
- }
-
- @Override
- protected int[] getAlertWindowTypes() {
- return ALERT_WINDOW_TYPES;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
deleted file mode 100755
index efc80ea..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.server.alertwindowappsdk25">
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
- <application android:label="CtsAlertWindowSdk25">
- <activity android:name=".AlertWindowTestActivitySdk25"
- android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
deleted file mode 100644
index 046879f..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.alertwindowappsdk25;
-
-import android.os.Bundle;
-
-import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
-import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-
-public class AlertWindowTestActivitySdk25 extends AlertWindowTestBaseActivity {
- private static final int[] ALERT_WINDOW_TYPES = {
- TYPE_PHONE,
- TYPE_PRIORITY_PHONE,
- TYPE_SYSTEM_ALERT,
- TYPE_SYSTEM_ERROR,
- TYPE_SYSTEM_OVERLAY
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- createAllAlertWindows(getPackageName());
- }
-
- @Override
- protected int[] getAlertWindowTypes() {
- return ALERT_WINDOW_TYPES;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
deleted file mode 100644
index 2d0dad0..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/src/android/server/alertwindowappsdk25/AlertWindowTestBaseActivity.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.alertwindowappsdk25;
-
-import android.app.Activity;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.util.Log;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.TOP;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-
-public abstract class AlertWindowTestBaseActivity extends Activity {
-
- protected void createAllAlertWindows(String windowName) {
- final int[] alertWindowTypes = getAlertWindowTypes();
- for (int type : alertWindowTypes) {
- try {
- createAlertWindow(type, windowName);
- } catch (Exception e) {
- Log.e("AlertWindowTestBaseActivity", "Can't create type=" + type, e);
- }
- }
- }
-
- protected void createAlertWindow(int type) {
- createAlertWindow(type, getPackageName());
- }
-
- protected void createAlertWindow(int type, String windowName) {
- if (!isSystemAlertWindowType(type)) {
- throw new IllegalArgumentException("Well...you are not an alert window type=" + type);
- }
-
- final Point size = new Point();
- final WindowManager wm = getSystemService(WindowManager.class);
- wm.getDefaultDisplay().getSize(size);
-
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- type, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
- params.width = size.x / 3;
- params.height = size.y / 3;
- params.gravity = TOP | LEFT;
- params.setTitle(windowName);
-
- final TextView view = new TextView(this);
- view.setText(windowName + " type=" + type);
- view.setBackgroundColor(Color.RED);
- wm.addView(view, params);
- }
-
- private boolean isSystemAlertWindowType(int type) {
- final int[] alertWindowTypes = getAlertWindowTypes();
- for (int current : alertWindowTypes) {
- if (current == type) {
- return true;
- }
- }
- return false;
- }
-
- protected abstract int[] getAlertWindowTypes();
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/Android.mk
deleted file mode 100644
index 12a04ed..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDragAndDropSourceApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/AndroidManifest.xml
deleted file mode 100644
index 296a979..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.wm.cts.dndsourceapp">
- <application android:label="CtsDnDSource">
- <activity android:name="android.wm.cts.dndsourceapp.DragSource">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
-
- <provider android:name="android.wm.cts.dndsourceapp.DragSourceContentProvider"
- android:authorities="android.wm.cts.dndsource.contentprovider"
- android:grantUriPermissions="true"/>
- </application>
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
deleted file mode 100644
index 7cb7b8b..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.wm.cts.dndsourceapp;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.FileUriExposedException;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
-
-import java.io.File;
-
-public class DragSource extends Activity{
- private static final String LOG_TAG = "DragSource";
-
- private static final String RESULT_KEY_START_DRAG = "START_DRAG";
- private static final String RESULT_KEY_DETAILS = "DETAILS";
- private static final String RESULT_OK = "OK";
- private static final String RESULT_EXCEPTION = "Exception";
-
- private static final String URI_PREFIX =
- "content://" + DragSourceContentProvider.AUTHORITY + "/data";
-
- private static final String MAGIC_VALUE = "42";
- private static final long TIMEOUT_CANCEL = 150;
-
- private TextView mTextView;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- View view = getLayoutInflater().inflate(R.layout.source_activity, null);
- setContentView(view);
-
- final Uri plainUri = Uri.parse(URI_PREFIX + "/" + MAGIC_VALUE);
-
- setUpDragSource("disallow_global", plainUri, 0);
- setUpDragSource("cancel_soon", plainUri, View.DRAG_FLAG_GLOBAL);
-
- setUpDragSource("grant_none", plainUri, View.DRAG_FLAG_GLOBAL);
- setUpDragSource("grant_read", plainUri,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
- setUpDragSource("grant_write", plainUri,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_WRITE);
- setUpDragSource("grant_read_persistable", plainUri,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
- View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION);
-
- final Uri prefixUri = Uri.parse(URI_PREFIX);
-
- setUpDragSource("grant_read_prefix", prefixUri,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
- View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION);
- setUpDragSource("grant_read_noprefix", prefixUri,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
-
- final Uri fileUri = Uri.fromFile(new File("/sdcard/sample.jpg"));
-
- setUpDragSource("file_local", fileUri, 0);
- setUpDragSource("file_global", fileUri, View.DRAG_FLAG_GLOBAL);
- }
-
- private void setUpDragSource(String mode, final Uri uri, final int flags) {
- if (!mode.equals(getIntent().getStringExtra("mode"))) {
- return;
- }
- mTextView = (TextView) findViewById(R.id.drag_source);
- mTextView.setText(mode);
- mTextView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
- try {
- final ClipDescription clipDescription = new ClipDescription("", new String[] {
- ClipDescription.MIMETYPE_TEXT_URILIST });
- PersistableBundle extras = new PersistableBundle(1);
- extras.putString("extraKey", "extraValue");
- clipDescription.setExtras(extras);
- final ClipData clipData = new ClipData(clipDescription, new ClipData.Item(uri));
- v.startDragAndDrop(
- clipData,
- new View.DragShadowBuilder(v),
- null,
- flags);
- logResult(RESULT_KEY_START_DRAG, RESULT_OK);
- } catch (FileUriExposedException e) {
- logResult(RESULT_KEY_DETAILS, e.getMessage());
- logResult(RESULT_KEY_START_DRAG, RESULT_EXCEPTION);
- }
- if (mode.equals("cancel_soon")) {
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- v.cancelDragAndDrop();
- }
- }, TIMEOUT_CANCEL);
- }
- return true;
- }
- });
- }
-
- private void logResult(String key, String value) {
- Log.i(LOG_TAG, key + "=" + value);
- mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
deleted file mode 100644
index 06a94aa..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.wm.cts.dndsourceapp;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.net.Uri;
-
-public class DragSourceContentProvider extends ContentProvider {
-
- public static final String AUTHORITY = "android.wm.cts.dndsource.contentprovider";
-
- private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int URI_DATA = 1;
-
- static {
- sMatcher.addURI(AUTHORITY, "data/#", URI_DATA);
- }
-
- @Override
- public boolean onCreate() {
- return false;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- switch (sMatcher.match(uri)) {
- case URI_DATA:
- return new DragSourceCursor(uri.getLastPathSegment());
- }
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
deleted file mode 100644
index c54df65..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.wm.cts.dndsourceapp;
-
-import android.database.AbstractCursor;
-
-public class DragSourceCursor extends AbstractCursor {
- private static final String COLUMN_KEY = "key";
-
- private final String mValue;
-
- public DragSourceCursor(String value) {
- mValue = value;
- }
-
- @Override
- public int getCount() {
- return 1;
- }
-
- @Override
- public String[] getColumnNames() {
- return new String[] {COLUMN_KEY};
- }
-
- @Override
- public String getString(int column) {
- if (getPosition() != 0) {
- throw new IllegalArgumentException("Incorrect position: " + getPosition());
- }
- if (column != 0) {
- throw new IllegalArgumentException("Incorrect column: " + column);
- }
- return mValue;
- }
-
- @Override
- public short getShort(int column) {
- return 0;
- }
-
- @Override
- public int getInt(int column) {
- return 0;
- }
-
- @Override
- public long getLong(int column) {
- return 0;
- }
-
- @Override
- public float getFloat(int column) {
- return 0;
- }
-
- @Override
- public double getDouble(int column) {
- return 0;
- }
-
- @Override
- public boolean isNull(int column) {
- return false;
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/Android.mk
deleted file mode 100644
index cb43a8b..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDragAndDropTargetApp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/AndroidManifest.xml
deleted file mode 100644
index ed7b9c2..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.wm.cts.dndtargetapp">
- <application android:label="CtsDnDTarget">
- <activity android:name="android.wm.cts.dndtargetapp.DropTarget">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
deleted file mode 100644
index 8892c6f..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.wm.cts.dndtargetapp;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.util.Log;
-import android.view.DragAndDropPermissions;
-import android.view.DragEvent;
-import android.view.View;
-import android.widget.TextView;
-
-public class DropTarget extends Activity {
- public static final String LOG_TAG = "DropTarget";
-
- private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
- private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
- private static final String RESULT_KEY_EXTRAS = "EXTRAS";
- private static final String RESULT_KEY_DROP_RESULT = "DROP";
- private static final String RESULT_KEY_DETAILS = "DETAILS";
- private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
- private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
- private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
- private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
- private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
-
- public static final String RESULT_OK = "OK";
- public static final String RESULT_EXCEPTION = "Exception";
- public static final String RESULT_MISSING = "MISSING";
- public static final String RESULT_LEAKING = "LEAKING";
-
- protected static final String MAGIC_VALUE = "42";
-
- private TextView mTextView;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- View view = getLayoutInflater().inflate(R.layout.target_activity, null);
- setContentView(view);
-
- setUpDropTarget("request_none", new OnDragUriReadListener(false));
- setUpDropTarget("request_read", new OnDragUriReadListener());
- setUpDropTarget("request_write", new OnDragUriWriteListener());
- setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
- setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
- }
-
- private void setUpDropTarget(String mode, OnDragUriListener listener) {
- if (!mode.equals(getIntent().getStringExtra("mode"))) {
- return;
- }
- mTextView = (TextView)findViewById(R.id.drag_target);
- mTextView.setText(mode);
- mTextView.setOnDragListener(listener);
- }
-
- private String checkExtraValue(DragEvent event) {
- PersistableBundle extras = event.getClipDescription().getExtras();
- if (extras == null) {
- return "Null";
- }
-
- final String value = extras.getString("extraKey");
- if ("extraValue".equals(value)) {
- return RESULT_OK;
- }
- return value;
- }
-
- private void logResult(String key, String value) {
- Log.i(LOG_TAG, key + "=" + value);
- mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
- }
-
- private abstract class OnDragUriListener implements View.OnDragListener {
- private final boolean requestPermissions;
-
- public OnDragUriListener(boolean requestPermissions) {
- this.requestPermissions = requestPermissions;
- }
-
- @Override
- public boolean onDrag(View v, DragEvent event) {
- checkDragEvent(event);
-
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
- logResult(RESULT_KEY_EXTRAS, checkExtraValue(event));
- return true;
-
- case DragEvent.ACTION_DRAG_ENTERED:
- return true;
-
- case DragEvent.ACTION_DRAG_LOCATION:
- return true;
-
- case DragEvent.ACTION_DRAG_EXITED:
- return true;
-
- case DragEvent.ACTION_DROP:
- // Try accessing the Uri without the permissions grant.
- accessContent(event, RESULT_KEY_ACCESS_BEFORE, false);
-
- // Try accessing the Uri with the permission grant (if required);
- accessContent(event, RESULT_KEY_DROP_RESULT, requestPermissions);
-
- // Try accessing the Uri after the permissions have been released.
- accessContent(event, RESULT_KEY_ACCESS_AFTER, false);
- return true;
-
- case DragEvent.ACTION_DRAG_ENDED:
- logResult(RESULT_KEY_DRAG_ENDED, RESULT_OK);
- return true;
-
- default:
- return false;
- }
- }
-
- private void accessContent(DragEvent event, String resultKey, boolean requestPermissions) {
- String result;
- try {
- result = processDrop(event, requestPermissions);
- } catch (SecurityException e) {
- result = RESULT_EXCEPTION;
- if (resultKey.equals(RESULT_KEY_DROP_RESULT)) {
- logResult(RESULT_KEY_DETAILS, e.getMessage());
- }
- }
- logResult(resultKey, result);
- }
-
- private String processDrop(DragEvent event, boolean requestPermissions) {
- final ClipData clipData = event.getClipData();
- if (clipData == null) {
- return "Null ClipData";
- }
- if (clipData.getItemCount() == 0) {
- return "Empty ClipData";
- }
- ClipData.Item item = clipData.getItemAt(0);
- if (item == null) {
- return "Null ClipData.Item";
- }
- Uri uri = item.getUri();
- if (uri == null) {
- return "Null Uri";
- }
-
- DragAndDropPermissions permissions = null;
- if (requestPermissions) {
- permissions = requestDragAndDropPermissions(event);
- if (permissions == null) {
- return "Null DragAndDropPermissions";
- }
- }
-
- try {
- return processUri(uri);
- } finally {
- if (permissions != null) {
- permissions.release();
- }
- }
- }
-
- abstract protected String processUri(Uri uri);
- }
-
- private void checkDragEvent(DragEvent event) {
- final int action = event.getAction();
-
- // ClipData should be available for ACTION_DROP only.
- final ClipData clipData = event.getClipData();
- if (action == DragEvent.ACTION_DROP) {
- if (clipData == null) {
- logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
- }
- } else {
- if (clipData != null) {
- logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_LEAKING + action);
- }
- }
-
- // ClipDescription should be always available except for ACTION_DRAG_ENDED.
- final ClipDescription clipDescription = event.getClipDescription();
- if (action != DragEvent.ACTION_DRAG_ENDED) {
- if (clipDescription == null) {
- logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING + action);
- }
- } else {
- if (clipDescription != null) {
- logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_LEAKING);
- }
- }
-
- // Local state should be always null for cross-app drags.
- final Object localState = event.getLocalState();
- if (localState != null) {
- logResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_LEAKING + action);
- }
- }
-
- private class OnDragUriReadListener extends OnDragUriListener {
- OnDragUriReadListener(boolean requestPermissions) {
- super(requestPermissions);
- }
-
- OnDragUriReadListener() {
- super(true);
- }
-
- protected String processUri(Uri uri) {
- return checkQueryResult(uri, MAGIC_VALUE);
- }
-
- protected String checkQueryResult(Uri uri, String expectedValue) {
- Cursor cursor = null;
- try {
- cursor = getContentResolver().query(uri, null, null, null, null);
- if (cursor == null) {
- return "Null Cursor";
- }
- cursor.moveToPosition(0);
- String value = cursor.getString(0);
- if (!expectedValue.equals(value)) {
- return "Wrong value: " + value;
- }
- return RESULT_OK;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
-
- private class OnDragUriWriteListener extends OnDragUriListener {
- OnDragUriWriteListener() {
- super(true);
- }
-
- protected String processUri(Uri uri) {
- ContentValues values = new ContentValues();
- values.put("key", 100);
- getContentResolver().update(uri, values, null, null);
- return RESULT_OK;
- }
- }
-
- private class OnDragUriReadPrefixListener extends OnDragUriReadListener {
- @Override
- protected String processUri(Uri uri) {
- final String result1 = queryPrefixed(uri, "1");
- if (!result1.equals(RESULT_OK)) {
- return result1;
- }
- final String result2 = queryPrefixed(uri, "2");
- if (!result2.equals(RESULT_OK)) {
- return result2;
- }
- return queryPrefixed(uri, "3");
- }
-
- private String queryPrefixed(Uri uri, String selector) {
- final Uri prefixedUri = Uri.parse(uri.toString() + "/" + selector);
- return checkQueryResult(prefixedUri, selector);
- }
- }
-
- private class OnDragUriTakePersistableListener extends OnDragUriListener {
- OnDragUriTakePersistableListener() {
- super(true);
- }
-
- @Override
- protected String processUri(Uri uri) {
- getContentResolver().takePersistableUriPermission(
- uri, View.DRAG_FLAG_GLOBAL_URI_READ);
- getContentResolver().releasePersistableUriPermission(
- uri, View.DRAG_FLAG_GLOBAL_URI_READ);
- return RESULT_OK;
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/Android.mk b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/Android.mk
deleted file mode 100644
index a33e1bc..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 23
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDragAndDropTargetAppSdk23
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/AndroidManifest.xml
deleted file mode 100644
index ea636a8..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.wm.cts.dndtargetappsdk23">
- <application android:label="CtsDnDTarget">
- <activity android:name="android.wm.cts.dndtargetappsdk23.DropTarget">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/src/android/wm/cts/dndtargetappsdk23/DropTarget.java b/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/src/android/wm/cts/dndtargetappsdk23/DropTarget.java
deleted file mode 100644
index 2cb7779..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/src/android/wm/cts/dndtargetappsdk23/DropTarget.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.wm.cts.dndtargetappsdk23;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.DragEvent;
-import android.view.View;
-import android.widget.TextView;
-
-/**
- * This application is compiled against SDK 23 and used to verify that apps targeting SDK 23 and
- * below do not receive global drags.
- */
-public class DropTarget extends Activity {
- public static final String LOG_TAG = "DropTarget";
-
- private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
- private static final String RESULT_KEY_DROP_RESULT = "DROP";
-
- public static final String RESULT_OK = "OK";
-
- private TextView mTextView;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- View view = getLayoutInflater().inflate(R.layout.target_activity, null);
- setContentView(view);
-
- mTextView = (TextView) findViewById(R.id.drag_target);
- mTextView.setOnDragListener(new OnDragListener());
- }
-
- private void logResult(String key, String value) {
- String result = key + "=" + value;
- Log.i(LOG_TAG, result);
- mTextView.setText(result);
- }
-
- private class OnDragListener implements View.OnDragListener {
- @Override
- public boolean onDrag(View v, DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
- return true;
-
- case DragEvent.ACTION_DRAG_ENTERED:
- return true;
-
- case DragEvent.ACTION_DRAG_LOCATION:
- return true;
-
- case DragEvent.ACTION_DRAG_EXITED:
- return true;
-
- case DragEvent.ACTION_DROP:
- logResult(RESULT_KEY_DROP_RESULT, RESULT_OK);
- return true;
-
- case DragEvent.ACTION_DRAG_ENDED:
- return true;
-
- default:
- return false;
- }
- }
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml
deleted file mode 100755
index 7f899c2..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="android.server.FrameTestApp">
- <application android:theme="@android:style/Theme.Material">
- <activity android:name=".DialogTestActivity"
- android:exported="true"
- />
- <activity android:name=".MovingChildTestActivity"
- android:exported="true"
- />
- </application>
-</manifest>
-
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java
deleted file mode 100644
index 593cf34..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.FrameTestApp;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.view.Window;
-import android.view.Gravity;
-
-public class DialogTestActivity extends Activity {
-
- AlertDialog mDialog;
-
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- }
-
- protected void onStop() {
- super.onStop();
- mDialog.dismiss();
- }
- protected void onResume() {
- super.onResume();
- setupTest(getIntent());
- }
-
- private void setupTest(Intent intent) {
- String testCase = intent.getStringExtra(
- "android.server.FrameTestApp.DialogTestCase");
- switch (testCase) {
- case "MatchParent": {
- testMatchParent();
- break;
- } case "MatchParentLayoutInOverscan": {
- testMatchParentLayoutInOverscan();
- } break;
- case "ExplicitSize": {
- testExplicitSize();
- break;
- }
- case "ExplicitSizeTopLeftGravity": {
- testExplicitSizeTopLeftGravity();
- break;
- }
- case "ExplicitSizeBottomRightGravity": {
- testExplicitSizeBottomRightGravity();
- break;
- }
- case "OversizedDimensions": {
- testOversizedDimensions();
- break;
- }
- case "OversizedDimensionsNoLimits": {
- testOversizedDimensionsNoLimits();
- break;
- }
- case "ExplicitPositionMatchParent": {
- testExplicitPositionMatchParent();
- break;
- }
- case "ExplicitPositionMatchParentNoLimits": {
- testExplicitPositionMatchParentNoLimits();
- break;
- }
- case "NoFocus": {
- testNoFocus();
- break;
- }
- case "WithMargins": {
- testWithMargins();
- break;
- }
- default:
- break;
- }
- }
-
- interface DialogLayoutParamsTest {
- void doSetup(WindowManager.LayoutParams p);
- }
-
- private void doLayoutParamTest(DialogLayoutParamsTest t) {
- mDialog = new AlertDialog.Builder(this).create();
-
- mDialog.setMessage("Testing is fun!");
- mDialog.setTitle("android.server.FrameTestApp/android.server.FrameTestApp.TestDialog");
- mDialog.create();
-
- Window w = mDialog.getWindow();
- final WindowManager.LayoutParams params = w.getAttributes();
- t.doSetup(params);
- w.setAttributes(params);
-
- mDialog.show();
- }
-
- private void testMatchParent() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
- });
- }
-
- private void testMatchParentLayoutInOverscan() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
- params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
- params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
- });
- }
-
- private void testExplicitSize() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = 200;
- params.height = 200;
- });
- }
-
- private void testExplicitSizeTopLeftGravity() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = 200;
- params.height = 200;
- params.gravity = Gravity.TOP | Gravity.LEFT;
- });
- }
-
- private void testExplicitSizeBottomRightGravity() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = 200;
- params.height = 200;
- params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
- });
- }
-
- private void testOversizedDimensions() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = 100000;
- params.height = 100000;
- });
- }
-
- private void testOversizedDimensionsNoLimits() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = 5000;
- params.height = 5000;
- params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
- params.gravity = Gravity.LEFT | Gravity.TOP;
- });
- }
-
- private void testExplicitPositionMatchParent() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
- params.x = 100;
- params.y = 100;
- });
- }
-
- private void testExplicitPositionMatchParentNoLimits() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.MATCH_PARENT;
- params.gravity = Gravity.LEFT | Gravity.TOP;
- params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
- params.x = 100;
- params.y = 100;
- });
- }
-
- private void testNoFocus() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- });
- }
-
- private void testWithMargins() {
- doLayoutParamTest((WindowManager.LayoutParams params) -> {
- params.gravity = Gravity.LEFT | Gravity.TOP;
- params.horizontalMargin = .25f;
- params.verticalMargin = .35f;
- params.width = 200;
- params.height = 200;
- params.x = 0;
- params.y = 0;
- });
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java b/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java
deleted file mode 100644
index de6f597..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.FrameTestApp;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.view.Window;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.Space;
-import android.widget.Button;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-// This activity will parent a Child to the main window, and then move
-// the main window around. We can use this to verify the Child
-// is properly updated.
-public class MovingChildTestActivity extends Activity {
- Space mView;
- int mX = 0;
- int mY = 0;
-
- final Runnable moveWindow = new Runnable() {
- @Override
- public void run() {
- final Window w = getWindow();
- final WindowManager.LayoutParams attribs = w.getAttributes();
- attribs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
- attribs.x = mX % 1000;
- attribs.y = mY % 1000;
- w.setAttributes(attribs);
- mX += 5;
- mY += 5;
- mView.postDelayed(this, 50);
- }
- };
-
- final Runnable makeChild = new Runnable() {
- @Override
- public void run() {
- Button b = new Button(MovingChildTestActivity.this);
- WindowManager.LayoutParams p = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_APPLICATION_PANEL);
- p.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
- p.x = 0;
- p.y = 0;
- p.token = mView.getWindowToken();
- p.setTitle("ChildWindow");
-
- ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).addView(b, p);
-
- mView.postDelayed(moveWindow, 50);
- }
- };
-
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final LayoutParams p = new LayoutParams(100, 100);
- final Window w = getWindow();
- w.setLayout(100, 100);
- mView = new Space(this);
-
- setContentView(mView, p);
- mView.post(makeChild);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
deleted file mode 100644
index bfed6d5..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.cts;
-
-import android.platform.test.annotations.Presubmit;
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Build: mmma -j32 cts/hostsidetests/services
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsWindowManagerHostTestCases android.server.cts.AlertWindowsTests
- */
-@Presubmit
-public class AlertWindowsTests extends ActivityManagerTestBase {
-
- private static final String PACKAGE_NAME = "android.server.alertwindowapp";
- private static final String ACTIVITY_NAME = "AlertWindowTestActivity";
- private static final String SDK_25_PACKAGE_NAME = "android.server.alertwindowappsdk25";
- private static final String SDK_25_ACTIVITY_NAME = "AlertWindowTestActivitySdk25";
-
- // From WindowManager.java
- private static final int TYPE_BASE_APPLICATION = 1;
- private static final int FIRST_SYSTEM_WINDOW = 2000;
-
- private static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW + 2;
- private static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3;
- private static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6;
- private static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7;
- private static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10;
- private static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
-
- private static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
- private static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11;
- private static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW + 19;
-
- private final List<Integer> mAlertWindowTypes = Arrays.asList(
- TYPE_PHONE,
- TYPE_PRIORITY_PHONE,
- TYPE_SYSTEM_ALERT,
- TYPE_SYSTEM_ERROR,
- TYPE_SYSTEM_OVERLAY,
- TYPE_APPLICATION_OVERLAY);
- private final List<Integer> mSystemWindowTypes = Arrays.asList(
- TYPE_STATUS_BAR,
- TYPE_INPUT_METHOD,
- TYPE_NAVIGATION_BAR);
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- try {
- setAlertWindowPermission(PACKAGE_NAME, false);
- setAlertWindowPermission(SDK_25_PACKAGE_NAME, false);
- executeShellCommand("am force-stop " + PACKAGE_NAME);
- executeShellCommand("am force-stop " + SDK_25_PACKAGE_NAME);
- } catch (DeviceNotAvailableException e) {
- }
- }
-
- public void testAlertWindowAllowed() throws Exception {
- runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, true /* hasAlertWindowPermission */,
- true /* atLeastO */);
- }
-
- public void testAlertWindowDisallowed() throws Exception {
- runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, false /* hasAlertWindowPermission */,
- true /* atLeastO */);
- }
-
- public void testAlertWindowAllowedSdk25() throws Exception {
- runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
- true /* hasAlertWindowPermission */, false /* atLeastO */);
- }
-
- public void testAlertWindowDisallowedSdk25() throws Exception {
- runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
- false /* hasAlertWindowPermission */, false /* atLeastO */);
- }
-
- private void runAlertWindowTest(String packageName, String activityName,
- boolean hasAlertWindowPermission, boolean atLeastO) throws Exception {
- setComponentName(packageName);
- setAlertWindowPermission(packageName, hasAlertWindowPermission);
-
- executeShellCommand(getAmStartCmd(activityName));
- mAmWmState.computeState(mDevice, new String[] { activityName });
- mAmWmState.assertVisibility(activityName, true);
-
- assertAlertWindows(packageName, hasAlertWindowPermission, atLeastO);
- }
-
- private void assertAlertWindows(String packageName, boolean hasAlertWindowPermission,
- boolean atLeastO) {
- final WindowManagerState wMState = mAmWmState.getWmState();
-
- final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList();
- wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
-
- if (!hasAlertWindowPermission) {
- assertTrue("Should be empty alertWindows=" + alertWindows, alertWindows.isEmpty());
- return;
- }
-
- if (atLeastO) {
- // Assert that only TYPE_APPLICATION_OVERLAY was created.
- for (WindowManagerState.WindowState win : alertWindows) {
- assertTrue("Can't create win=" + win + " on SDK O or greater",
- win.getType() == TYPE_APPLICATION_OVERLAY);
- }
- }
-
- final WindowManagerState.WindowState mainAppWindow =
- wMState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
-
- assertNotNull(mainAppWindow);
-
- wMState.sortWindowsByLayer(alertWindows);
- final WindowManagerState.WindowState lowestAlertWindow = alertWindows.get(0);
- final WindowManagerState.WindowState highestAlertWindow =
- alertWindows.get(alertWindows.size() - 1);
-
- // Assert that the alert windows have higher z-order than the main app window
- assertTrue("lowestAlertWindow=" + lowestAlertWindow + " less than mainAppWindow="
- + mainAppWindow, lowestAlertWindow.getLayer() > mainAppWindow.getLayer());
-
- // Assert that legacy alert windows have a lower z-order than the new alert window layer.
- final WindowManagerState.WindowState appOverlayWindow =
- wMState.getWindowByPackageName(packageName, TYPE_APPLICATION_OVERLAY);
- if (appOverlayWindow != null && highestAlertWindow != appOverlayWindow) {
- assertTrue("highestAlertWindow=" + highestAlertWindow
- + " greater than appOverlayWindow=" + appOverlayWindow,
- highestAlertWindow.getLayer() < appOverlayWindow.getLayer());
- }
-
- // Assert that alert windows are below key system windows.
- final ArrayList<WindowManagerState.WindowState> systemWindows = new ArrayList();
- wMState.getWindowsByPackageName(packageName, mSystemWindowTypes, systemWindows);
- if (!systemWindows.isEmpty()) {
- wMState.sortWindowsByLayer(systemWindows);
- final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
- assertTrue("highestAlertWindow=" + highestAlertWindow
- + " greater than lowestSystemWindow=" + lowestSystemWindow,
- highestAlertWindow.getLayer() < lowestSystemWindow.getLayer());
- }
- }
-
- private void setAlertWindowPermission(String packageName, boolean allow) throws Exception {
- executeShellCommand("appops set " + packageName + " android:system_alert_window "
- + (allow ? "allow" : "deny"));
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ChildMovementTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ChildMovementTests.java
deleted file mode 100644
index 218fcfe..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ChildMovementTests.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import static android.server.cts.StateLogger.logE;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.awt.Rectangle;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import android.server.cts.ActivityManagerTestBase;
-import android.server.cts.WindowManagerState.WindowState;
-
-public class ChildMovementTests extends ParentChildTestBase {
- private List<WindowState> mWindowList = new ArrayList();
-
- @Override
- String intentKey() {
- return "android.server.FrameTestApp.ChildTestCase";
- }
-
- @Override
- String activityName() {
- return "MovingChildTestActivity";
- }
-
- WindowState getSingleWindow(String fullWindowName) {
- try {
- mAmWmState.getWmState().getMatchingVisibleWindowState(fullWindowName, mWindowList);
- return mWindowList.get(0);
- } catch (Exception e) {
- CLog.logAndDisplay(LogLevel.INFO, "Couldn't find window: " + fullWindowName);
- return null;
- }
- }
-
- WindowState getSingleWindowByPrefix(String prefix) {
- try {
- mAmWmState.getWmState().getPrefixMatchingVisibleWindowState(prefix, mWindowList);
- return mWindowList.get(0);
- } catch (Exception e) {
- CLog.logAndDisplay(LogLevel.INFO, "Couldn't find window: " + prefix);
- return null;
- }
- }
-
- void doSingleTest(ParentChildTest t) throws Exception {
- String popupName = "ChildWindow";
- final String[] waitForVisible = new String[] { popupName };
-
- mAmWmState.setUseActivityNamesForWindowNames(false);
- mAmWmState.computeState(mDevice, waitForVisible);
- WindowState popup = getSingleWindowByPrefix(popupName);
- WindowState parent = getSingleWindow(getBaseWindowName() + activityName());
-
- t.doTest(parent, popup);
- }
-
-
- Object monitor = new Object();
- boolean testPassed = false;
- String popupName = null;
- String mainName = null;
-
- SurfaceTraceReceiver.SurfaceObserver observer = new SurfaceTraceReceiver.SurfaceObserver() {
- int transactionCount = 0;
- boolean sawChildMove = false;
- boolean sawMainMove = false;
- int timesSeen = 0;
-
- @Override
- public void openTransaction() {
- transactionCount++;
- if (transactionCount == 1) {
- sawChildMove = false;
- sawMainMove = false;
- }
- }
-
- @Override
- public void closeTransaction() {
- transactionCount--;
- if (transactionCount != 0) {
- return;
- }
- synchronized (monitor) {
- if (sawChildMove ^ sawMainMove ) {
- monitor.notifyAll();
- return;
- }
- if (timesSeen > 10) {
- testPassed = true;
- monitor.notifyAll();
- }
- }
- }
-
- @Override
- public void setPosition(String windowName, float x, float y) {
- if (windowName.equals(popupName)) {
- sawChildMove = true;
- timesSeen++;
- } else if (windowName.equals(mainName)) {
- sawMainMove = true;
- }
- }
- };
-
- /**
- * Here we test that a Child moves in the same transaction
- * as its parent. We launch an activity with a Child which will
- * move around its own main window. Then we listen to WindowManager transactions.
- * Since the Child is static within the window, if we ever see one of
- * them move xor the other one we have a problem!
- */
- public void testSurfaceMovesWithParent() throws Exception {
- doFullscreenTest("MovesWithParent",
- (WindowState parent, WindowState popup) -> {
- popupName = popup.getName();
- mainName = parent.getName();
- installSurfaceObserver(observer);
- try {
- synchronized (monitor) {
- monitor.wait(5000);
- }
- } catch (InterruptedException e) {
- } finally {
- assertTrue(testPassed);
- removeSurfaceObserver();
- }
- });
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/CrossAppDragAndDropTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/CrossAppDragAndDropTests.java
deleted file mode 100644
index 17ede35..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/CrossAppDragAndDropTests.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.tradefed.device.CollectingOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-public class CrossAppDragAndDropTests extends DeviceTestCase {
- // Constants copied from ActivityManager.StackId. If they are changed there, these must be
- // updated.
- /** ID of stack where fullscreen activities are normally launched into. */
- private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
- /** ID of stack where freeform/resized activities are normally launched into. */
- private static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
- /** ID of stack that occupies a dedicated region of the screen. */
- private static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
- /** ID of stack that always on top (always visible) when it exists. */
- private static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
- private static final String AM_FORCE_STOP = "am force-stop ";
- private static final String AM_MOVE_TASK = "am stack move-task ";
- private static final String AM_RESIZE_TASK = "am task resize ";
- private static final String AM_REMOVE_STACK = "am stack remove ";
- private static final String AM_START_N = "am start -n ";
- private static final String AM_STACK_LIST = "am stack list";
- private static final String INPUT_MOUSE_SWIPE = "input mouse swipe ";
- private static final String TASK_ID_PREFIX = "taskId";
-
- // Regex pattern to match adb shell am stack list output of the form:
- // taskId=<TASK_ID>: <componentName> bounds=[LEFT,TOP][RIGHT,BOTTOM]
- private static final String TASK_REGEX_PATTERN_STRING =
- "taskId=[0-9]+: %s bounds=\\[[0-9]+,[0-9]+\\]\\[[0-9]+,[0-9]+\\]";
-
- private static final int SWIPE_DURATION_MS = 500;
-
- private static final String SOURCE_PACKAGE_NAME = "android.wm.cts.dndsourceapp";
- private static final String TARGET_PACKAGE_NAME = "android.wm.cts.dndtargetapp";
- private static final String TARGET_23_PACKAGE_NAME = "android.wm.cts.dndtargetappsdk23";
-
-
- private static final String SOURCE_ACTIVITY_NAME = "DragSource";
- private static final String TARGET_ACTIVITY_NAME = "DropTarget";
-
- private static final String FILE_GLOBAL = "file_global";
- private static final String FILE_LOCAL = "file_local";
- private static final String DISALLOW_GLOBAL = "disallow_global";
- private static final String CANCEL_SOON = "cancel_soon";
- private static final String GRANT_NONE = "grant_none";
- private static final String GRANT_READ = "grant_read";
- private static final String GRANT_WRITE = "grant_write";
- private static final String GRANT_READ_PREFIX = "grant_read_prefix";
- private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
- private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
-
- private static final String REQUEST_NONE = "request_none";
- private static final String REQUEST_READ = "request_read";
- private static final String REQUEST_READ_NESTED = "request_read_nested";
- private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
- private static final String REQUEST_WRITE = "request_write";
-
- private static final String SOURCE_LOG_TAG = "DragSource";
- private static final String TARGET_LOG_TAG = "DropTarget";
-
- private static final String RESULT_KEY_START_DRAG = "START_DRAG";
- private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
- private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
- private static final String RESULT_KEY_EXTRAS = "EXTRAS";
- private static final String RESULT_KEY_DROP_RESULT = "DROP";
- private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
- private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
- private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
- private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
- private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
-
- private static final String RESULT_MISSING = "Missing";
- private static final String RESULT_OK = "OK";
- private static final String RESULT_EXCEPTION = "Exception";
- private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
-
- private static final String AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW =
- "am supports-split-screen-multi-window";
-
- private ITestDevice mDevice;
-
- private Map<String, String> mSourceResults;
- private Map<String, String> mTargetResults;
-
- private String mSourcePackageName;
- private String mTargetPackageName;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mDevice = getDevice();
-
- if (!supportsDragAndDrop()) {
- return;
- }
-
- mSourcePackageName = SOURCE_PACKAGE_NAME;
- mTargetPackageName = TARGET_PACKAGE_NAME;
- cleanupState();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- if (!supportsDragAndDrop()) {
- return;
- }
-
- mDevice.executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
- mDevice.executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
- }
-
- private String executeShellCommand(String command) throws DeviceNotAvailableException {
- return mDevice.executeShellCommand(command);
- }
-
- private void clearLogs() throws DeviceNotAvailableException {
- executeShellCommand("logcat -c");
- }
-
- private String getStartCommand(String componentName, String modeExtra) {
- return AM_START_N + componentName + " -e mode " + modeExtra;
- }
-
- private String getMoveTaskCommand(int taskId, int stackId) throws Exception {
- return AM_MOVE_TASK + taskId + " " + stackId + " true";
- }
-
- private String getResizeTaskCommand(int taskId, Point topLeft, Point bottomRight)
- throws Exception {
- return AM_RESIZE_TASK + taskId + " " + topLeft.x + " " + topLeft.y + " " + bottomRight.x
- + " " + bottomRight.y;
- }
-
- private String getComponentName(String packageName, String activityName) {
- return packageName + "/" + packageName + "." + activityName;
- }
-
- /**
- * Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
- * is in a good state.
- */
- private void cleanupState() throws Exception {
- executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
- executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
- executeShellCommand(AM_FORCE_STOP + TARGET_23_PACKAGE_NAME);
- unlockDevice();
-
- // Reinitialize the docked stack to force the window manager to reset its default bounds.
- // See b/29068935.
- clearLogs();
- final String componentName = getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME);
- executeShellCommand(getStartCommand(componentName, null) + " --stack " +
- FULLSCREEN_WORKSPACE_STACK_ID);
- final int taskId = getActivityTaskId(componentName);
- // Moving a task from the full screen stack to the docked stack resets
- // WindowManagerService#mDockedStackCreateBounds.
- executeShellCommand(getMoveTaskCommand(taskId, DOCKED_STACK_ID));
- waitForResume(mSourcePackageName, SOURCE_ACTIVITY_NAME);
- executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
-
- // Remove special stacks.
- executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
- executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
- executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
- }
-
- private void launchDockedActivity(String packageName, String activityName, String mode)
- throws Exception {
- clearLogs();
- final String componentName = getComponentName(packageName, activityName);
- executeShellCommand(getStartCommand(componentName, mode) + " --stack " + DOCKED_STACK_ID);
- waitForResume(packageName, activityName);
- }
-
- private void launchFullscreenActivity(String packageName, String activityName, String mode)
- throws Exception {
- clearLogs();
- final String componentName = getComponentName(packageName, activityName);
- executeShellCommand(getStartCommand(componentName, mode) + " --stack "
- + FULLSCREEN_WORKSPACE_STACK_ID);
- waitForResume(packageName, activityName);
- }
-
- /**
- * @param displaySize size of the display
- * @param leftSide {@code true} to launch the app taking up the left half of the display,
- * {@code false} to launch the app taking up the right half of the display.
- */
- private void launchFreeformActivity(String packageName, String activityName, String mode,
- Point displaySize, boolean leftSide) throws Exception{
- clearLogs();
- final String componentName = getComponentName(packageName, activityName);
- executeShellCommand(getStartCommand(componentName, mode) + " --stack "
- + FREEFORM_WORKSPACE_STACK_ID);
- waitForResume(packageName, activityName);
- Point topLeft = new Point(leftSide ? 0 : displaySize.x / 2, 0);
- Point bottomRight = new Point(leftSide ? displaySize.x / 2 : displaySize.x, displaySize.y);
- executeShellCommand(getResizeTaskCommand(getActivityTaskId(componentName), topLeft,
- bottomRight));
- }
-
- private void waitForResume(String packageName, String activityName) throws Exception {
- final String fullActivityName = packageName + "." + activityName;
- int retryCount = 3;
- do {
- Thread.sleep(500);
- String logs = executeShellCommand("logcat -d -b events");
- for (String line : logs.split("\\n")) {
- if(line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
- return;
- }
- }
- } while (retryCount-- > 0);
-
- throw new Exception(fullActivityName + " has failed to start");
- }
-
- private void injectInput(Point from, Point to, int durationMs) throws Exception {
- executeShellCommand(
- INPUT_MOUSE_SWIPE + from.x + " " + from.y + " " + to.x + " " + to.y + " " +
- durationMs);
- }
-
- static class Point {
- public int x, y;
-
- public Point(int _x, int _y) {
- x=_x;
- y=_y;
- }
-
- public Point() {}
- }
-
- private String findTaskInfo(String name) throws Exception {
- CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
- mDevice.executeShellCommand(AM_STACK_LIST, outputReceiver);
- final String output = outputReceiver.getOutput();
- final StringBuilder builder = new StringBuilder();
- builder.append("Finding task info for task: ");
- builder.append(name);
- builder.append("\nParsing adb shell am output: " );
- builder.append(output);
- CLog.i(builder.toString());
- final Pattern pattern = Pattern.compile(String.format(TASK_REGEX_PATTERN_STRING, name));
- for (String line : output.split("\\n")) {
- final String truncatedLine;
- // Only look for the activity name before the "topActivity" string.
- final int pos = line.indexOf("topActivity");
- if (pos > 0) {
- truncatedLine = line.substring(0, pos);
- } else {
- truncatedLine = line;
- }
- if (pattern.matcher(truncatedLine).find()) {
- return truncatedLine;
- }
- }
- return "";
- }
-
- private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
- final String taskInfo = findTaskInfo(name);
- final String[] sections = taskInfo.split("\\[");
- if (sections.length > 2) {
- try {
- parsePoint(sections[1], from);
- parsePoint(sections[2], to);
- return true;
- } catch (Exception e) {
- return false;
- }
- }
- return false;
- }
-
- private int getActivityTaskId(String name) throws Exception {
- final String taskInfo = findTaskInfo(name);
- for (String word : taskInfo.split("\\s+")) {
- if (word.startsWith(TASK_ID_PREFIX)) {
- final String withColon = word.split("=")[1];
- return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
- }
- }
- return -1;
- }
-
- private Point getDisplaySize() throws Exception {
- final String output = executeShellCommand("wm size");
- final String[] sizes = output.split(" ")[2].split("x");
- return new Point(Integer.valueOf(sizes[0].trim()), Integer.valueOf(sizes[1].trim()));
- }
-
- private Point getWindowCenter(String name) throws Exception {
- Point p1 = new Point();
- Point p2 = new Point();
- if (getWindowBounds(name, p1, p2)) {
- return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
- }
- return null;
- }
-
- private void parsePoint(String string, Point point) {
- final String[] parts = string.split("[,|\\]]");
- point.x = Integer.parseInt(parts[0]);
- point.y = Integer.parseInt(parts[1]);
- }
-
- private void unlockDevice() throws DeviceNotAvailableException {
- // Wake up the device, if necessary.
- executeShellCommand("input keyevent 224");
- // Unlock the screen.
- executeShellCommand("input keyevent 82");
- }
-
- private Map<String, String> getLogResults(String className, String lastResultKey)
- throws Exception {
- int retryCount = 10;
- Map<String, String> output = new HashMap<String, String>();
- do {
-
- String logs = executeShellCommand("logcat -v brief -d " + className + ":I" + " *:S");
- for (String line : logs.split("\\n")) {
- if (line.startsWith("I/" + className)) {
- String payload = line.split(":")[1].trim();
- final String[] split = payload.split("=");
- if (split.length > 1) {
- output.put(split[0], split[1]);
- }
- }
- }
- if (output.containsKey(lastResultKey)) {
- return output;
- }
- } while (retryCount-- > 0);
- return output;
- }
-
- private void assertDropResult(String sourceMode, String targetMode, String expectedDropResult)
- throws Exception {
- assertDragAndDropResults(sourceMode, targetMode, RESULT_OK, expectedDropResult, RESULT_OK);
- }
-
- private void assertNoGlobalDragEvents(String sourceMode, String expectedStartDragResult)
- throws Exception {
- assertDragAndDropResults(
- sourceMode, REQUEST_NONE, expectedStartDragResult, RESULT_MISSING, RESULT_MISSING);
- }
-
- private void assertDragAndDropResults(String sourceMode, String targetMode,
- String expectedStartDragResult, String expectedDropResult,
- String expectedListenerResults) throws Exception {
- if (!supportsDragAndDrop()) {
- return;
- }
-
- if (supportsSplitScreenMultiWindow()) {
- launchDockedActivity(mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode);
- launchFullscreenActivity(mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode);
- } else if (supportsFreeformMultiWindow()) {
- // Fallback to try to launch two freeform windows side by side.
- Point displaySize = getDisplaySize();
- launchFreeformActivity(mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode,
- displaySize, true /* leftSide */);
- launchFreeformActivity(mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode,
- displaySize, false /* leftSide */);
- } else {
- return;
- }
-
- clearLogs();
-
- injectInput(
- getWindowCenter(getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME)),
- getWindowCenter(getComponentName(mTargetPackageName, TARGET_ACTIVITY_NAME)),
- SWIPE_DURATION_MS);
-
- mSourceResults = getLogResults(SOURCE_LOG_TAG, RESULT_KEY_START_DRAG);
- assertSourceResult(RESULT_KEY_START_DRAG, expectedStartDragResult);
-
- mTargetResults = getLogResults(TARGET_LOG_TAG, RESULT_KEY_DRAG_ENDED);
- assertTargetResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
- if (!RESULT_MISSING.equals(expectedDropResult)) {
- assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
- assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
- }
- assertListenerResults(expectedListenerResults);
- }
-
- private void assertListenerResults(String expectedResult) throws Exception {
- assertTargetResult(RESULT_KEY_DRAG_STARTED, expectedResult);
- assertTargetResult(RESULT_KEY_DRAG_ENDED, expectedResult);
- assertTargetResult(RESULT_KEY_EXTRAS, expectedResult);
-
- assertTargetResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
- assertTargetResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING);
- assertTargetResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_MISSING);
- }
-
- private void assertSourceResult(String resultKey, String expectedResult) throws Exception {
- assertResult(mSourceResults, resultKey, expectedResult);
- }
-
- private void assertTargetResult(String resultKey, String expectedResult) throws Exception {
- assertResult(mTargetResults, resultKey, expectedResult);
- }
-
- private void assertResult(Map<String, String> results, String resultKey, String expectedResult)
- throws Exception {
- if (!supportsDragAndDrop()) {
- return;
- }
-
- if (RESULT_MISSING.equals(expectedResult)) {
- if (results.containsKey(resultKey)) {
- fail("Unexpected " + resultKey + "=" + results.get(resultKey));
- }
- } else {
- assertTrue("Missing " + resultKey, results.containsKey(resultKey));
- assertEquals(resultKey + " result mismatch,", expectedResult,
- results.get(resultKey));
- }
- }
-
- private boolean supportsDragAndDrop() throws Exception {
- String supportsMultiwindow = mDevice.executeShellCommand("am supports-multiwindow").trim();
- if ("true".equals(supportsMultiwindow)) {
- return true;
- } else if ("false".equals(supportsMultiwindow)) {
- return false;
- } else {
- throw new Exception(
- "device does not support \"am supports-multiwindow\" shell command.");
- }
- }
-
- private boolean supportsSplitScreenMultiWindow() throws DeviceNotAvailableException {
- return !executeShellCommand(AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW).startsWith("false");
- }
-
- private boolean supportsFreeformMultiWindow() throws DeviceNotAvailableException {
- return mDevice.hasFeature("feature:android.software.freeform_window_management");
- }
-
- public void testCancelSoon() throws Exception {
- assertDropResult(CANCEL_SOON, REQUEST_NONE, RESULT_MISSING);
- }
-
- public void testDisallowGlobal() throws Exception {
- assertNoGlobalDragEvents(DISALLOW_GLOBAL, RESULT_OK);
- }
-
- public void testDisallowGlobalBelowSdk24() throws Exception {
- mTargetPackageName = TARGET_23_PACKAGE_NAME;
- assertNoGlobalDragEvents(GRANT_NONE, RESULT_OK);
- }
-
- public void testFileUriLocal() throws Exception {
- assertNoGlobalDragEvents(FILE_LOCAL, RESULT_OK);
- }
-
- public void testFileUriGlobal() throws Exception {
- assertNoGlobalDragEvents(FILE_GLOBAL, RESULT_EXCEPTION);
- }
-
- public void testGrantNoneRequestNone() throws Exception {
- assertDropResult(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
- }
-
- public void testGrantNoneRequestRead() throws Exception {
- assertDropResult(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
- }
-
- public void testGrantNoneRequestWrite() throws Exception {
- assertDropResult(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
- }
-
- public void testGrantReadRequestNone() throws Exception {
- assertDropResult(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
- }
-
- public void testGrantReadRequestRead() throws Exception {
- assertDropResult(GRANT_READ, REQUEST_READ, RESULT_OK);
- }
-
- public void testGrantReadRequestWrite() throws Exception {
- assertDropResult(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
- }
-
- public void testGrantReadNoPrefixRequestReadNested() throws Exception {
- assertDropResult(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
- }
-
- public void testGrantReadPrefixRequestReadNested() throws Exception {
- assertDropResult(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
- }
-
- public void testGrantPersistableRequestTakePersistable() throws Exception {
- assertDropResult(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
- }
-
- public void testGrantReadRequestTakePersistable() throws Exception {
- assertDropResult(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
- }
-
- public void testGrantWriteRequestNone() throws Exception {
- assertDropResult(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
- }
-
- public void testGrantWriteRequestRead() throws Exception {
- assertDropResult(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
- }
-
- public void testGrantWriteRequestWrite() throws Exception {
- assertDropResult(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/DialogFrameTests.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/DialogFrameTests.java
deleted file mode 100644
index c99f001..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/DialogFrameTests.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.awt.Rectangle;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import android.server.cts.WindowManagerState.WindowState;
-
-public class DialogFrameTests extends ParentChildTestBase {
- private List<WindowState> mWindowList = new ArrayList();
-
- @Override
- String intentKey() {
- return "android.server.FrameTestApp.DialogTestCase";
- }
-
- @Override
- String activityName() {
- return "DialogTestActivity";
- }
-
- WindowState getSingleWindow(String windowName) {
- try {
- mAmWmState.getWmState().getMatchingVisibleWindowState(
- getBaseWindowName() + windowName, mWindowList);
- return mWindowList.get(0);
- } catch (Exception e) {
- CLog.logAndDisplay(LogLevel.INFO, "Couldn't find window: " + windowName);
- return null;
- }
- }
-
- void doSingleTest(ParentChildTest t) throws Exception {
- final String[] waitForVisible = new String[] { "TestDialog" };
-
- mAmWmState.computeState(mDevice, waitForVisible);
- WindowState dialog = getSingleWindow("TestDialog");
- WindowState parent = getSingleWindow("DialogTestActivity");
-
- t.doTest(parent, dialog);
- }
-
- // With Width and Height as MATCH_PARENT we should fill
- // the same content frame as the main activity window
- public void testMatchParentDialog() throws Exception {
- doParentChildTest("MatchParent",
- (WindowState parent, WindowState dialog) -> {
- assertEquals(parent.getContentFrame(), dialog.getFrame());
- });
- }
-
- // If we have LAYOUT_IN_SCREEN and LAYOUT_IN_OVERSCAN with MATCH_PARENT,
- // we will not be constrained to the insets and so we will be the same size
- // as the main window main frame.
- public void testMatchParentDialogLayoutInOverscan() throws Exception {
- doParentChildTest("MatchParentLayoutInOverscan",
- (WindowState parent, WindowState dialog) -> {
- assertEquals(parent.getFrame(), dialog.getFrame());
- });
- }
-
- static final int explicitDimension = 200;
-
- // The default gravity for dialogs should center them.
- public void testExplicitSizeDefaultGravity() throws Exception {
- doParentChildTest("ExplicitSize",
- (WindowState parent, WindowState dialog) -> {
- Rectangle contentFrame = parent.getContentFrame();
- Rectangle expectedFrame = new Rectangle(
- contentFrame.x + (contentFrame.width - explicitDimension)/2,
- contentFrame.y + (contentFrame.height - explicitDimension)/2,
- explicitDimension, explicitDimension);
- assertEquals(expectedFrame, dialog.getFrame());
- });
- }
-
- public void testExplicitSizeTopLeftGravity() throws Exception {
- doParentChildTest("ExplicitSizeTopLeftGravity",
- (WindowState parent, WindowState dialog) -> {
- Rectangle contentFrame = parent.getContentFrame();
- Rectangle expectedFrame = new Rectangle(
- contentFrame.x,
- contentFrame.y,
- explicitDimension,
- explicitDimension);
- assertEquals(expectedFrame, dialog.getFrame());
- });
- }
-
- public void testExplicitSizeBottomRightGravity() throws Exception {
- doParentChildTest("ExplicitSizeBottomRightGravity",
- (WindowState parent, WindowState dialog) -> {
- Rectangle contentFrame = parent.getContentFrame();
- Rectangle expectedFrame = new Rectangle(
- contentFrame.x + contentFrame.width - explicitDimension,
- contentFrame.y + contentFrame.height - explicitDimension,
- explicitDimension, explicitDimension);
- assertEquals(expectedFrame, dialog.getFrame());
- });
- }
-
- // TODO: Commented out for now because it doesn't work. We end up
- // insetting the decor on the bottom. I think this is a bug
- // probably in the default dialog flags:
- // b/30127373
- // public void testOversizedDimensions() throws Exception {
- // doParentChildTest("OversizedDimensions",
- // (WindowState parent, WindowState dialog) -> {
- // With the default flags oversize should result in clipping to
- // parent frame.
- // assertEquals(parent.getContentFrame(), dialog.getFrame());
- // });
- // }
-
- // TODO(b/63993863) : Disabled pending public API to fetch maximum surface size.
- // static final int oversizedDimension = 5000;
- // With FLAG_LAYOUT_NO_LIMITS we should get the size we request, even if its much
- // larger than the screen.
- // public void testOversizedDimensionsNoLimits() throws Exception {
- // TODO(b/36890978): We only run this in fullscreen because of the
- // unclear status of NO_LIMITS for non-child surfaces in MW modes
- // doFullscreenTest("OversizedDimensionsNoLimits",
- // (WindowState parent, WindowState dialog) -> {
- // Rectangle contentFrame = parent.getContentFrame();
- // Rectangle expectedFrame = new Rectangle(contentFrame.x, contentFrame.y,
- // oversizedDimension, oversizedDimension);
- // assertEquals(expectedFrame, dialog.getFrame());
- // });
- // }
-
- // If we request the MATCH_PARENT and a non-zero position, we wouldn't be
- // able to fit all of our content, so we should be adjusted to just fit the
- // content frame.
- public void testExplicitPositionMatchParent() throws Exception {
- doParentChildTest("ExplicitPositionMatchParent",
- (WindowState parent, WindowState dialog) -> {
- assertEquals(parent.getContentFrame(),
- dialog.getFrame());
- });
- }
-
- // Unless we pass NO_LIMITS in which case our requested position should
- // be honored.
- public void testExplicitPositionMatchParentNoLimits() throws Exception {
- final int explicitPosition = 100;
- doParentChildTest("ExplicitPositionMatchParentNoLimits",
- (WindowState parent, WindowState dialog) -> {
- Rectangle contentFrame = parent.getContentFrame();
- Rectangle expectedFrame = new Rectangle(contentFrame.x + explicitPosition,
- contentFrame.y + explicitPosition,
- contentFrame.width,
- contentFrame.height);
- });
- }
-
- // We run the two focus tests fullscreen only because switching to the
- // docked stack will strip away focus from the task anyway.
- public void testDialogReceivesFocus() throws Exception {
- doFullscreenTest("MatchParent",
- (WindowState parent, WindowState dialog) -> {
- assertEquals(dialog.getName(), mAmWmState.getWmState().getFocusedWindow());
- });
- }
-
- public void testNoFocusDialog() throws Exception {
- doFullscreenTest("NoFocus",
- (WindowState parent, WindowState dialog) -> {
- assertEquals(parent.getName(), mAmWmState.getWmState().getFocusedWindow());
- });
- }
-
- public void testMarginsArePercentagesOfContentFrame() throws Exception {
- float horizontalMargin = .25f;
- float verticalMargin = .35f;
- doParentChildTest("WithMargins",
- (WindowState parent, WindowState dialog) -> {
- Rectangle frame = parent.getContentFrame();
- Rectangle expectedFrame = new Rectangle(
- (int)(horizontalMargin*frame.width + frame.x),
- (int)(verticalMargin*frame.height + frame.y),
- explicitDimension,
- explicitDimension);
- assertEquals(expectedFrame, dialog.getFrame());
- });
- }
-
- public void testDialogPlacedAboveParent() throws Exception {
- doParentChildTest("MatchParent",
- (WindowState parent, WindowState dialog) -> {
- // Not only should the dialog be higher, but it should be
- // leave multiple layers of space inbetween for DimLayers,
- // etc...
- assertTrue(dialog.getLayer() - parent.getLayer() >= 5);
- });
- }
-}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ParentChildTestBase.java b/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ParentChildTestBase.java
deleted file mode 100644
index c8ca9f4..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/ParentChildTestBase.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.cts;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import android.server.cts.WindowManagerState.WindowState;
-import android.server.cts.ActivityManagerTestBase;
-
-public abstract class ParentChildTestBase extends ActivityManagerTestBase {
- private static final String COMPONENT_NAME = "android.server.FrameTestApp";
-
- interface ParentChildTest {
- void doTest(WindowState parent, WindowState child);
- }
-
- public void startTestCase(String testCase) throws Exception {
- setComponentName(COMPONENT_NAME);
- String cmd = getAmStartCmd(activityName(), intentKey(), testCase);
- CLog.logAndDisplay(LogLevel.INFO, cmd);
- executeShellCommand(cmd);
- }
-
- public void startTestCaseDocked(String testCase) throws Exception {
- setComponentName(COMPONENT_NAME);
- String cmd = getAmStartCmd(activityName(), intentKey(), testCase);
- CLog.logAndDisplay(LogLevel.INFO, cmd);
- executeShellCommand(cmd);
- moveActivityToDockStack(activityName());
- }
-
- abstract String intentKey();
- abstract String activityName();
-
- abstract void doSingleTest(ParentChildTest t) throws Exception;
-
- void doFullscreenTest(String testCase, ParentChildTest t) throws Exception {
- CLog.logAndDisplay(LogLevel.INFO, "Running test fullscreen");
- startTestCase(testCase);
- doSingleTest(t);
- stopTestCase();
- }
-
- void doDockedTest(String testCase, ParentChildTest t) throws Exception {
- CLog.logAndDisplay(LogLevel.INFO, "Running test docked");
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no split multi-window support");
- return;
- }
- startTestCaseDocked(testCase);
- doSingleTest(t);
- stopTestCase();
- }
-
- void doParentChildTest(String testCase, ParentChildTest t) throws Exception {
- doFullscreenTest(testCase, t);
- doDockedTest(testCase, t);
- }
-}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
index d6077a4..d7517cd 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
@@ -37,6 +37,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
index 75b3ca0..d84c7b5 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.content.pm.cts.shortcut.backup.launcher1">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
index b82bb1d..e76cf28 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
@@ -17,6 +17,7 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import android.content.pm.ShortcutInfo;
import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
@@ -57,9 +58,22 @@
.selectByIds("ms1")
.areAllNotPinned();
+ // Package3 doesn't support backup&restore.
+ // However, the manifest-shortcuts will be republished anyway, so they're still pinned.
+ // The dynamic shortcuts can't be restored, but we'll still restore them as disabled
+ // shortcuts that are not visible to the publisher.
assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
- .haveIds("ms1", "ms2")
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllPinned()
+
+ .selectByIds("ms1", "ms2")
.areAllEnabled()
- .areAllNotPinned(); // P3 doesn't get backed up, so no longer pinned.
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+
+ .revertToOriginalList()
+ .selectByIds("s1", "s2")
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED)
+ ;
}
}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
index 8f124a0..8a6440d 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
@@ -37,6 +37,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
index 71ffc61..9297220 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.content.pm.cts.shortcut.backup.launcher2">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
index 75c79c5..115c476 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
@@ -17,6 +17,7 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import android.content.pm.ShortcutInfo;
import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
@@ -51,8 +52,22 @@
.selectByIds("ms2")
.areAllNotPinned();
+ // Package3 doesn't support backup&restore.
+ // However, the manifest-shortcuts will be republished anyway, so they're still pinned.
+ // The dynamic shortcuts can't be restored, but we'll still restore them as disabled
+ // shortcuts that are not visible to the publisher.
assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
- .haveIds("ms1", "ms2")
- .areAllEnabled();
+ .haveIds("ms1", "ms2", "s2", "s3")
+ .areAllPinned()
+
+ .selectByIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+
+ .revertToOriginalList()
+ .selectByIds("s2", "s3")
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED)
+ ;
}
}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
index 9883e5c..be8edb3 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
@@ -37,6 +37,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
index 82dc28f..8f1c1a7 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.content.pm.cts.shortcut.backup.launcher3">
<application android:allowBackup="false">
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk
new file mode 100644
index 0000000..97287fb
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher4new
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../launcher4old/src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.xml
new file mode 100644
index 0000000..1032971
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.launcher4"
+ android:versionCode="11">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.launcher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.mk
new file mode 100644
index 0000000..6389d22
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupLauncher4old
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.xml
new file mode 100644
index 0000000..e7c81b3
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.launcher4"
+ android:versionCode="10">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.launcher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/MainActivity.java
new file mode 100644
index 0000000..33215d6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/MainActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher4;
+
+import android.app.Activity;
+import android.content.pm.LauncherApps;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+public class MainActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Just accept all requests.
+ if (LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT.equals(getIntent().getAction())) {
+ LauncherApps.PinItemRequest request = getSystemService(LauncherApps.class)
+ .getPinItemRequest(getIntent());
+ final PersistableBundle extras = request.getShortcutInfo().getExtras();
+ if (extras != null && extras.getBoolean("acceptit")) {
+ request.accept();
+ }
+ }
+ finish();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..f9ecfef
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testRestoredOnOldVersion() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllPinned()
+
+ .selectByIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllManifest()
+
+ // s1 is re-published, so it's enabled and dynamic.
+ .revertToOriginalList()
+ .selectByIds("s1")
+ .areAllEnabled()
+ .areAllDynamic()
+ .forAllShortcuts(si -> {
+ assertEquals("shortlabel1_new_one", si.getShortLabel());
+
+ // The app re-published the shortcut, not updated, so the fields that existed
+ // in the original shortcut is gone now.
+ assertNull(si.getExtras());
+ })
+
+ .revertToOriginalList()
+ .selectByIds("s2")
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+ .forAllShortcuts(si -> {
+ // Note the label shouldn't be updated with the updateShortcuts() call.
+ assertEquals("shortlabel2", si.getShortLabel());
+ })
+ .areAllNotDynamic();
+ }
+
+ public void testRestoredOnNewVersion() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllPinned()
+
+ .selectByIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllManifest()
+
+ // s1 is re-published, so it's enabled and dynamic.
+ .revertToOriginalList()
+ .selectByIds("s1", "s2")
+ .areAllEnabled()
+
+ .revertToOriginalList()
+ .selectByIds("s1")
+ .areAllDynamic()
+ .forAllShortcuts(si -> {
+ assertEquals("shortlabel1_new_one", si.getShortLabel());
+
+ // The app re-published the shortcut, not updated, so the fields that existed
+ // in the original shortcut is gone now.
+ assertNull(si.getExtras());
+ })
+
+ .revertToOriginalList()
+ .selectByIds("s2")
+ .areAllNotDynamic()
+ .forAllShortcuts(si -> {
+ assertEquals("shortlabel2_updated", si.getShortLabel());
+ })
+ ;
+ }
+
+
+ public void testRestoreWrongKey() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllPinned()
+
+ .selectByIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllManifest()
+
+ // s1 is re-published, so it's enabled and dynamic.
+ .revertToOriginalList()
+ .selectByIds("s1")
+ .areAllEnabled()
+ .areAllDynamic()
+ .forAllShortcuts(si -> {
+ assertEquals("shortlabel1_new_one", si.getShortLabel());
+
+ // The app re-published the shortcut, not updated, so the fields that existed
+ // in the original shortcut is gone now.
+ assertNull(si.getExtras());
+ })
+
+
+ // updateShortcuts() shouldn't work on it, so it keeps the original label.
+ .revertToOriginalList()
+ .selectByIds("s2")
+ .areAllNotDynamic()
+ .forAllShortcuts(si -> {
+ assertEquals("shortlabel2", si.getShortLabel());
+ })
+ ;
+ }
+
+ public void testRestoreNoManifestOnOldVersion() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllPinned()
+
+ .selectByIds("s1", "ms1")
+ .areAllEnabled()
+ .areAllDynamic()
+ .areAllMutable()
+
+ .revertToOriginalList()
+ .selectByIds("s2", "ms2")
+ .areAllDisabled();
+ }
+
+ public void testRestoreNoManifestOnNewVersion() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllPinned()
+
+ .selectByIds("ms1", "ms2")
+ .areAllDisabled()
+ .areAllImmutable()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_APP_CHANGED)
+
+ .revertToOriginalList()
+ .selectByIds("s1", "s2")
+ .areAllEnabled()
+ .areAllMutable();
+ }
+
+ public void testInvisibleIgnored() {
+ assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER4_PKG))
+ .haveIds("ms1", "ms2", "s1", "s2")
+
+ .selectByIds("ms1", "s1", "s2")
+ .areAllPinned()
+ .areAllDisabled()
+ .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+ .areAllNotDynamic()
+ .areAllNotManifest()
+
+ .revertToOriginalList()
+ .selectByIds("ms2")
+ .areAllEnabled()
+ .areAllPinned()
+ .areAllNotDynamic()
+ .areAllNotManifest();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..8d99255
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.launcher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ static final String PUBLISHER4_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher4";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setAsDefaultLauncher(MainActivity.class);
+ }
+
+ public void testPreBackup() {
+ // Pin all the shortcuts.
+ getLauncherApps().pinShortcuts(PUBLISHER4_PKG, list("s1", "s2", "ms1", "ms2"),
+ getUserHandle());
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
index 72da903..ac91c82 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
@@ -37,6 +37,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
index 3d23c72..f35fcef 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
@@ -18,6 +18,8 @@
package="android.content.pm.cts.shortcut.backup.publisher1">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
index 286dded..2bb7f06 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
@@ -37,6 +37,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
index c3271b4..e4c5720 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
@@ -18,6 +18,8 @@
package="android.content.pm.cts.shortcut.backup.publisher2">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
index f1421d1..9a5f530 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
@@ -37,6 +37,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
index 5e03ff8..9f47f2a 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
@@ -18,6 +18,8 @@
package="android.content.pm.cts.shortcut.backup.publisher3">
<application android:allowBackup="false">
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="MainActivity" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
index 870dab9..33c8f82 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
@@ -21,16 +21,17 @@
public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
public void testWithUninstall() {
+ // backup = false, so dynamic shouldn't be restored.
assertWith(getManager().getDynamicShortcuts())
.isEmpty();
- // backup = false, so no pinned shortcuts should be restored.
+ // But manifest shortcuts will be restored anyway, so they'll restored as pinned.
assertWith(getManager().getPinnedShortcuts())
- .isEmpty();
+ .haveIds("ms1", "ms2");
assertWith(getManager().getManifestShortcuts())
.haveIds("ms1", "ms2")
- .areAllNotPinned();
+ .areAllPinned();
}
public void testWithNoUninstall() {
@@ -39,8 +40,8 @@
.haveIds("s1", "s2", "s3")
.areAllNotPinned();
+ // Manifest shortcuts should still be published.
assertWith(getManager().getManifestShortcuts())
- .haveIds("ms1", "ms2")
- .areAllNotPinned();
+ .haveIds("ms1", "ms2");
}
}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.mk
new file mode 100644
index 0000000..71a545c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.xml
new file mode 100644
index 0000000..c671d95
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="11">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.mk
new file mode 100644
index 0000000..1e35a01
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new_nobackup
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.xml
new file mode 100644
index 0000000..e176c81
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="11">
+
+ <application android:allowBackup="false">
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.mk
new file mode 100644
index 0000000..321377e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new_nomanifest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.xml
new file mode 100644
index 0000000..a08611a
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="11">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.mk
new file mode 100644
index 0000000..76410e8
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.mk
@@ -0,0 +1,48 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4new_wrongkey
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.xml
new file mode 100644
index 0000000..e176c81
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="11">
+
+ <application android:allowBackup="false">
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.mk
new file mode 100644
index 0000000..7341d66
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4old
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.xml
new file mode 100644
index 0000000..0c9f4ab
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="10">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.publisher4.MainActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/values/strings.xml
new file mode 100644
index 0000000..5872ed1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="label1">Shortcut 1</string>
+ <string name="long_label1">Long shortcut label1</string>
+ <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+ <string name="label2">Shortcut 2</string>
+ <string name="long_label2">Long shortcut label2</string>
+ <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+ <string name="label3">Shortcut 3</string>
+ <string name="long_label3">Long shortcut label3</string>
+ <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+ <string name="label4">Shortcut 4</string>
+ <string name="long_label4">Long shortcut label4</string>
+ <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+ <string name="label5">Shortcut 5</string>
+ <string name="long_label5">Long shortcut label5</string>
+ <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/xml/shortcuts.xml
new file mode 100644
index 0000000..11cede6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/res/xml/shortcuts.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+ <shortcut
+ android:shortcutId="ms1"
+ android:icon="@drawable/black_16x16"
+ android:shortcutShortLabel="@string/label1"
+ android:shortcutLongLabel="@string/long_label1"
+ android:shortcutDisabledMessage="@string/disabled_message1">
+ <intent
+ android:action="android.intent.action.VIEW" />
+ <categories android:name="android.shortcut.conversation" />
+ <categories android:name="android.shortcut.media" />
+ </shortcut>
+ <shortcut
+ android:shortcutId="ms2"
+ android:shortcutShortLabel="@string/label2">
+ android:shortcutLongLabel="@string/long_label2">
+ <intent android:action="action" />
+ <intent android:action="action2"
+ android:data="data"
+ android:mimeType="a/b"
+ android:targetPackage="pkg"
+ android:targetClass="pkg.class"
+ >
+ <categories android:name="icat1"/>
+ <categories android:name="icat2"/>
+ <extra android:name="key1" android:value="value1" />
+ <extra android:name="key2" android:value="123" />
+ <extra android:name="key3" android:value="true" />
+ </intent>
+ </shortcut>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/MainActivity.java
new file mode 100644
index 0000000..62acf9d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/MainActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher4;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..224c8ba
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.os.PersistableBundle;
+import android.text.TextUtils;
+
+import java.security.SecureRandom;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testRestoredOnOldVersion() {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllPinned()
+ .areAllEnabled();
+
+ // At this point, s1 and s2 don't look to exist to the publisher, so it can publish a
+ // dynamic shortcut and that should work.
+ // But updateShortcuts() don't.
+
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1_new_one")
+ .setActivity(getActivity("MainActivity"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2_updated")
+ .build();
+ assertTrue(getManager().updateShortcuts(list(s2)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1") // s2 not in the list.
+ .areAllEnabled();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1", "ms2", "s1") // s2 not in the list.
+ .areAllEnabled();
+
+ }
+
+ public void testRestoredOnNewVersion() {
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1")
+ .areAllEnabled()
+ .areAllPinned();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllPinned()
+ .areAllEnabled();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllEnabled();
+
+ // This time, update should work.
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2_updated")
+ .build();
+ assertTrue(getManager().updateShortcuts(list(s2)));
+
+ assertWith(getManager().getPinnedShortcuts())
+ .selectByIds("s2")
+ .forAllShortcuts(si -> {
+ assertEquals("shortlabel2_updated", si.getShortLabel());
+ });
+ }
+
+ public void testBackupDisabled() {
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllEnabled();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllPinned()
+ .areAllEnabled();
+
+ // Backup was disabled, so either s1 nor s2 look to be restored to the app.
+ // So when the app publishes s1, it'll be published as new.
+ // But it'll still be pinned.
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1_new_one")
+ .setActivity(getActivity("MainActivity"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1") // s2 not in the list.
+ .areAllPinned()
+ .areAllEnabled()
+ .forAllShortcuts(si -> {
+ // The app re-published the shortcut, not updated, so the fields that existed
+ // in the original shortcut is gone now.
+ assertNull(si.getExtras());
+ });
+ }
+
+ public void testRestoreWrongKey() {
+ // Restored pinned shortcuts are from a package with a different signature, so the dynamic
+ // pinned shortcuts should be disabled-invisible.
+
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1", "ms2");
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllPinned()
+ .areAllEnabled();
+
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1_new_one")
+ .setActivity(getActivity("MainActivity"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2_updated")
+ .build();
+ assertTrue(getManager().updateShortcuts(list(s2)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1") // s2 not in the list.
+ .areAllEnabled();
+ }
+
+ /**
+ * Restored on an older version that have no manifest shortcuts.
+ *
+ * In this case, the publisher wouldn't see the manifest shortcuts, and they're overwritable.
+ */
+ public void testRestoreNoManifestOnOldVersion() {
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .isEmpty();
+
+ // ms1 was manifest/immutable, but can be overwritten.
+ final ShortcutInfo ms1 = new ShortcutInfo.Builder(getContext(), "ms1")
+ .setShortLabel("ms1_new_one")
+ .setActivity(getActivity("MainActivity"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(ms1)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("ms1");
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1");
+
+ // Adding s1 should also work.
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("s1_new_one")
+ .setActivity(getActivity("MainActivity"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().addDynamicShortcuts(list(s1)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "ms1");
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("s1", "ms1");
+
+ // Update on ms2 should be no-op.
+ final ShortcutInfo ms2 = new ShortcutInfo.Builder(getContext(), "ms2")
+ .setShortLabel("ms2-updated")
+ .build();
+ assertTrue(getManager().updateShortcuts(list(ms2)));
+
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "ms1")
+ .areAllEnabled()
+ .areAllPinned()
+ .areAllMutable()
+
+ .selectByIds("s1")
+ .forAllShortcuts(si -> {
+ assertEquals("s1_new_one", si.getShortLabel());
+ })
+
+ .revertToOriginalList()
+ .selectByIds("ms1")
+ .forAllShortcuts(si -> {
+ assertEquals("ms1_new_one", si.getShortLabel());
+ });
+ }
+
+ /**
+ * Restored on the same (or newer) version that have no manifest shortcuts.
+ *
+ * In this case, the publisher will see the manifest shortcuts (as immutable pinned),
+ * and they *cannot* be overwritten.
+ */
+ public void testRestoreNoManifestOnNewVersion() {
+ assertWith(getManager().getManifestShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getDynamicShortcuts())
+ .isEmpty();
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .selectByIds("ms1", "ms2")
+ .areAllDisabled()
+
+ .revertToOriginalList()
+ .selectByIds("s1", "s2")
+ .areAllEnabled();
+ }
+
+ private void assertNoShortcuts() {
+ assertWith(getManager().getDynamicShortcuts()).isEmpty();
+ assertWith(getManager().getPinnedShortcuts()).isEmpty();
+ assertWith(getManager().getManifestShortcuts()).isEmpty();
+ }
+
+ public void testInvisibleIgnored() throws Exception {
+ assertNoShortcuts();
+
+ // Make sure "disable" won't change the disabled reason. Also make sure "enable" won't
+ // enable them.
+ getManager().disableShortcuts(list("s1", "s2", "ms1"));
+ assertNoShortcuts();
+
+ getManager().enableShortcuts(list("ms1", "s2"));
+ assertNoShortcuts();
+
+ getManager().enableShortcuts(list("ms1"));
+ assertNoShortcuts();
+
+ getManager().removeDynamicShortcuts(list("s1", "ms1"));
+ assertNoShortcuts();
+
+ getManager().removeAllDynamicShortcuts();
+ assertNoShortcuts();
+
+
+ // Force launcher 4 to be the default launcher so it'll receive the pin request.
+ setDefaultLauncher(getInstrumentation(),
+ "android.content.pm.cts.shortcut.backup.launcher4/.MainActivity");
+
+ // Update, set and add have been tested already, so let's test "pin".
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putBoolean("acceptit", true);
+
+ final ShortcutInfo ms2 = new ShortcutInfo.Builder(getContext(), "ms2")
+ .setShortLabel("ms2_new_one")
+ .setActivity(getActivity("MainActivity"))
+ .setIntents(new Intent[]{new Intent("main2")})
+ .setExtras(pb)
+ .build();
+
+ final String myIntentAction = "cts-shortcut-intent_" + new SecureRandom().nextInt();
+ final IntentFilter myFilter = new IntentFilter(myIntentAction);
+
+ final BroadcastReceiver onResult = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ }
+ };
+ getContext().registerReceiver(onResult, myFilter);
+ assertTrue(getManager().requestPinShortcut(ms2,
+ PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
+ PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender()));
+
+ assertTrue("Didn't receive requestPinShortcut() callback.",
+ latch.await(30, TimeUnit.SECONDS));
+
+ assertWith(getManager().getPinnedShortcuts())
+ .haveIds("ms2")
+ .areAllNotDynamic()
+ .areAllNotManifest()
+ .areAllMutable()
+ .areAllPinned()
+ .forAllShortcuts(si -> {
+ // requestPinShortcut() acts as an update in this case, so even though
+ // the original shortcut hada long label, this one does not.
+ assertTrue(TextUtils.isEmpty(si.getLongLabel()));
+ });
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..15b3853
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.backup.publisher4;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+ public void testPreBackup() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getContext().getResources(), R.drawable.black_16x64));
+ final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+ final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+ .setShortLabel("shortlabel1")
+ .setLongLabel("longlabel1")
+ .setIcon(icon1)
+ .setActivity(getActivity("MainActivity"))
+ .setDisabledMessage("disabledmessage1")
+ .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+ .setExtras(makePersistableBundle("ek1", "ev1"))
+ .setCategories(set("cat1"))
+ .build();
+
+ final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+ .setShortLabel("shortlabel2")
+ .setActivity(getActivity("MainActivity2"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+ .setShortLabel("shortlabel3")
+ .setIcon(icon3)
+ .setActivity(getActivity("MainActivity2"))
+ .setIntents(new Intent[]{new Intent("main")})
+ .build();
+
+ assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+ assertWith(getManager().getDynamicShortcuts())
+ .haveIds("s1", "s2", "s3")
+ .areAllNotPinned();
+
+ assertWith(getManager().getManifestShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllNotPinned();
+ }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.mk
new file mode 100644
index 0000000..35e2152
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsShortcutBackupPublisher4old_nomanifest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../publisher4old/src) \
+ $(call all-java-files-under, ../../common/src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../publisher4old/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target-minus-junit4 \
+ compatibility-device-util \
+ ctstestrunner \
+ ub-uiautomator \
+ ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.xml
new file mode 100644
index 0000000..af47b93
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="10">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.content.pm.cts.shortcut.backup.publisher4" />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/Android.mk b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
index 619bdfe..dc1d018 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
@@ -39,6 +39,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
index 3fbed5f..886aded 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
@@ -18,6 +18,8 @@
package="android.content.pm.cts.shortcut.multiuser">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="Launcher" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.mk b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
index ae0bf75..d7e13b6 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
@@ -43,6 +43,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
@@ -74,6 +76,8 @@
ub-uiautomator \
ShortcutManagerTestUtils
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
index 1b88d5e..72d7dfc 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
+++ b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
@@ -18,6 +18,8 @@
package="android.content.pm.cts.shortcut.upgrade">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="Launcher" android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
index 8b53b2e..b060940 100644
--- a/hostsidetests/shortcuts/hostside/AndroidTest.xml
+++ b/hostsidetests/shortcuts/hostside/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS ShortcutManager host tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="android.cts.backup.BackupPreparer">
<option name="enable-backup-if-needed" value="true" />
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
index c703e7f..9b8d98a 100644
--- a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
@@ -41,6 +41,8 @@
abstract public class BaseShortcutManagerHostTest extends DeviceTestCase implements IBuildReceiver {
protected static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH TRUE
+ protected static final boolean NO_UNINSTALL_IN_TEARDOWN = false; // DO NOT SUBMIT WITH TRUE
+
private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
private IBuildInfo mCtsBuild;
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
index 960b41f..3caacad 100644
--- a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
@@ -26,9 +26,22 @@
private static final String LAUNCHER1_APK = "CtsShortcutBackupLauncher1.apk";
private static final String LAUNCHER2_APK = "CtsShortcutBackupLauncher2.apk";
private static final String LAUNCHER3_APK = "CtsShortcutBackupLauncher3.apk";
+ private static final String LAUNCHER4_OLD_APK = "CtsShortcutBackupLauncher4old.apk";
+ private static final String LAUNCHER4_NEW_APK = "CtsShortcutBackupLauncher4new.apk";
+
private static final String PUBLISHER1_APK = "CtsShortcutBackupPublisher1.apk";
private static final String PUBLISHER2_APK = "CtsShortcutBackupPublisher2.apk";
private static final String PUBLISHER3_APK = "CtsShortcutBackupPublisher3.apk";
+ private static final String PUBLISHER4_OLD_APK = "CtsShortcutBackupPublisher4old.apk";
+ private static final String PUBLISHER4_NEW_APK = "CtsShortcutBackupPublisher4new.apk";
+ private static final String PUBLISHER4_NEW_NOBACKUP_APK
+ = "CtsShortcutBackupPublisher4new_nobackup.apk";
+ private static final String PUBLISHER4_NEW_WRONGKEY_APK
+ = "CtsShortcutBackupPublisher4new_wrongkey.apk";
+ private static final String PUBLISHER4_OLD_NO_MANIFST_APK
+ = "CtsShortcutBackupPublisher4old_nomanifest.apk";
+ private static final String PUBLISHER4_NEW_NO_MANIFST_APK
+ = "CtsShortcutBackupPublisher4new_nomanifest.apk";
private static final String LAUNCHER1_PKG =
"android.content.pm.cts.shortcut.backup.launcher1";
@@ -36,12 +49,17 @@
"android.content.pm.cts.shortcut.backup.launcher2";
private static final String LAUNCHER3_PKG =
"android.content.pm.cts.shortcut.backup.launcher3";
+ private static final String LAUNCHER4_PKG =
+ "android.content.pm.cts.shortcut.backup.launcher4";
+
private static final String PUBLISHER1_PKG =
"android.content.pm.cts.shortcut.backup.publisher1";
private static final String PUBLISHER2_PKG =
"android.content.pm.cts.shortcut.backup.publisher2";
private static final String PUBLISHER3_PKG =
"android.content.pm.cts.shortcut.backup.publisher3";
+ private static final String PUBLISHER4_PKG =
+ "android.content.pm.cts.shortcut.backup.publisher4";
private static final int BROADCAST_TIMEOUT_SECONDS = 120;
@@ -59,16 +77,22 @@
clearShortcuts(LAUNCHER1_PKG, getPrimaryUserId());
clearShortcuts(LAUNCHER2_PKG, getPrimaryUserId());
clearShortcuts(LAUNCHER3_PKG, getPrimaryUserId());
+ clearShortcuts(LAUNCHER4_PKG, getPrimaryUserId());
+
clearShortcuts(PUBLISHER1_PKG, getPrimaryUserId());
clearShortcuts(PUBLISHER2_PKG, getPrimaryUserId());
clearShortcuts(PUBLISHER3_PKG, getPrimaryUserId());
+ clearShortcuts(PUBLISHER4_PKG, getPrimaryUserId());
uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER1_PKG);
uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER2_PKG);
uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER3_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+
uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER1_PKG);
uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER2_PKG);
uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER3_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
waitUntilPackagesGone();
}
@@ -80,14 +104,16 @@
dumpsys("tearDown");
}
- if (mSupportsBackup) {
+ if (mSupportsBackup && !NO_UNINSTALL_IN_TEARDOWN) {
getDevice().uninstallPackage(LAUNCHER1_PKG);
getDevice().uninstallPackage(LAUNCHER2_PKG);
getDevice().uninstallPackage(LAUNCHER3_PKG);
+ getDevice().uninstallPackage(LAUNCHER4_PKG);
getDevice().uninstallPackage(PUBLISHER1_PKG);
getDevice().uninstallPackage(PUBLISHER2_PKG);
getDevice().uninstallPackage(PUBLISHER3_PKG);
+ getDevice().uninstallPackage(PUBLISHER4_PKG);
}
super.tearDown();
@@ -169,8 +195,8 @@
CLog.i("Waiting until all packages are removed from shortcut manager...");
final String packages[] = {
- LAUNCHER1_PKG, LAUNCHER2_PKG, LAUNCHER3_PKG,
- PUBLISHER1_PKG, PUBLISHER2_PKG, PUBLISHER3_PKG,
+ LAUNCHER1_PKG, LAUNCHER2_PKG, LAUNCHER3_PKG, LAUNCHER4_PKG,
+ PUBLISHER1_PKG, PUBLISHER2_PKG, PUBLISHER3_PKG, PUBLISHER4_PKG,
};
String dumpsys = "";
@@ -184,9 +210,11 @@
if (dumpsys.contains("Launcher: " + LAUNCHER1_PKG)) continue;
if (dumpsys.contains("Launcher: " + LAUNCHER2_PKG)) continue;
if (dumpsys.contains("Launcher: " + LAUNCHER3_PKG)) continue;
+ if (dumpsys.contains("Launcher: " + LAUNCHER4_PKG)) continue;
if (dumpsys.contains("Package: " + PUBLISHER1_PKG)) continue;
if (dumpsys.contains("Package: " + PUBLISHER2_PKG)) continue;
if (dumpsys.contains("Package: " + PUBLISHER3_PKG)) continue;
+ if (dumpsys.contains("Package: " + PUBLISHER4_PKG)) continue;
dumpsys("Shortcut manager handled broadcasts");
@@ -298,6 +326,353 @@
getPrimaryUserId());
}
+ public void testBackupAndRestore_downgrade() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from the new version and pin them.
+
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Restore the old version of the app, and the launcher.
+ // (But we don't check the launcher's version, so using old is fine.)
+ installAppAsUser(LAUNCHER4_OLD_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_OLD_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoredOnOldVersion",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoredOnOldVersion",
+ getPrimaryUserId());
+
+ // New install the original version. All blocked shortcuts should re-appear.
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoredOnNewVersion",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoredOnNewVersion",
+ getPrimaryUserId());
+
+ }
+
+ public void testBackupAndRestore_backupWasDisabled() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from "nobackup" version.
+
+ installAppAsUser(PUBLISHER4_NEW_NOBACKUP_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Install the "backup-ok" version. But restoration is limited.
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testBackupDisabled",
+ getPrimaryUserId());
+ }
+
+ public void testBackupAndRestore_backupIsDisabled() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from backup-ok version.
+
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Install the nobackup version. Restoration is limited.
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_NEW_NOBACKUP_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testBackupDisabled",
+ getPrimaryUserId());
+ }
+
+ public void testBackupAndRestore_wrongKey() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from backup-ok version.
+
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Install the nobackup version. Restoration is limited.
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_NEW_WRONGKEY_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoreWrongKey",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoreWrongKey",
+ getPrimaryUserId());
+ }
+
+ public void testBackupAndRestore_noManifestOnOldVersion() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from backup-ok version.
+
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Install the nobackup version. Restoration is limited.
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_OLD_NO_MANIFST_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoreNoManifestOnOldVersion",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoreNoManifestOnOldVersion",
+ getPrimaryUserId());
+ }
+
+ public void testBackupAndRestore_noManifestOnNewVersion() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from backup-ok version.
+
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Install the nobackup version. Restoration is limited.
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_NEW_NO_MANIFST_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoreNoManifestOnNewVersion",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testRestoreNoManifestOnNewVersion",
+ getPrimaryUserId());
+ }
+
+ /**
+ * Make sure invisible shortcuts are ignored by all API calls.
+ *
+ * (Restore from new to old-nomanifest)
+ */
+ public void testBackupAndRestore_invisibleIgnored() throws Exception {
+ if (!mSupportsBackup) {
+ return;
+ }
+ dumpsys("Test start");
+
+ // First, publish shortcuts from backup-ok version.
+
+ installAppAsUser(PUBLISHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+ dumpsys("Before backup");
+
+ // Backup
+ doBackup();
+
+ // Uninstall all apps
+ uninstallPackageAndWaitUntilBroadcastsDrain(LAUNCHER4_PKG);
+ uninstallPackageAndWaitUntilBroadcastsDrain(PUBLISHER4_PKG);
+
+ // Make sure the shortcut service handled all the uninstall broadcasts.
+ waitUntilPackagesGone();
+
+ // Do it one more time just in case...
+ waitUntilBroadcastsDrain();
+
+ // Then restore
+ doRestore();
+
+ dumpsys("After restore");
+
+ // Install the nobackup version. Restoration is limited.
+ installAppAsUser(LAUNCHER4_NEW_APK, getPrimaryUserId());
+ installAppAsUser(PUBLISHER4_OLD_NO_MANIFST_APK, getPrimaryUserId());
+ waitUntilBroadcastsDrain();
+
+ runDeviceTestsAsUser(PUBLISHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testInvisibleIgnored",
+ getPrimaryUserId());
+
+ runDeviceTestsAsUser(LAUNCHER4_PKG, ".ShortcutManagerPostBackupTest",
+ "testInvisibleIgnored",
+ getPrimaryUserId());
+ }
+
public void testBackupAndRestore_withNoUninstall() throws Exception {
if (!mSupportsBackup) {
return;
diff --git a/hostsidetests/statsd/Android.mk b/hostsidetests/statsd/Android.mk
new file mode 100644
index 0000000..b614cb1
--- /dev/null
+++ b/hostsidetests/statsd/Android.mk
@@ -0,0 +1,33 @@
+# 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)
+
+# tag this module as a cts test artifact
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := CtsStatsdHostTestCases
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_STATIC_JAVA_LIBRARIES := platformprotos
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util host-libprotobuf-java-full
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/statsd/AndroidTest.xml b/hostsidetests/statsd/AndroidTest.xml
new file mode 100644
index 0000000..4a0f150
--- /dev/null
+++ b/hostsidetests/statsd/AndroidTest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Statsd host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="statsd" />
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsStatsdHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/statsd/apps/Android.mk b/hostsidetests/statsd/apps/Android.mk
new file mode 100644
index 0000000..4a74e80
--- /dev/null
+++ b/hostsidetests/statsd/apps/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.mk b/hostsidetests/statsd/apps/statsdapp/Android.mk
new file mode 100644
index 0000000..58e509e
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/Android.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsStatsdAtomsApp
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit org.apache.http.legacy
+
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ctstestrunner \
+ compatibility-device-util \
+ android-support-v4 \
+ legacy-android-test \
+ android-support-test
+
+LOCAL_SDK_VERSION := test_current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
new file mode 100644
index 0000000..5544294
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.cts.device.statsd" >
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.CAMERA"/>
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.DUMP" /> <!-- must be granted via pm grant -->
+
+
+ <application android:label="@string/app_name">
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="org.apache.http.legacy" android:required="false" />
+ <activity
+ android:name=".VideoPlayerActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleTop" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.cts.device.statsd"
+ android:label="CTS tests of android.os.statsd stats collection">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>/>
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidTest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidTest.xml
new file mode 100644
index 0000000..402a73b
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Statsd test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="misc" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsStatsdAtomsApp.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="pm grant com.android.server.cts.device.statsd android.permission.DUMP" />
+ <option name="teardown-command" value="pm revoke com.android.server.cts.device.statsd android.permission.DUMP"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.cts.device.statsd" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
new file mode 100644
index 0000000..a029c80
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/video_frame"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <VideoView
+ android:id="@+id/video_player_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
+ </FrameLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4 b/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
new file mode 100644
index 0000000..0bec670
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3 b/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
new file mode 100644
index 0000000..d20f772
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml b/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
new file mode 100644
index 0000000..e40d2ac
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">CTS Statsd Atoms App</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
new file mode 100644
index 0000000..a2f7bcf
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.media.MediaPlayer;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class AtomTests{
+ private static final String TAG = AtomTests.class.getSimpleName();
+
+ @Test
+ public void testAudioState() {
+ Context context = InstrumentationRegistry.getContext();
+ MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.good);
+ mediaPlayer.start();
+ sleep(2_000);
+ mediaPlayer.stop();
+ }
+
+ @Test
+ public void testBleScanOptimized() {
+ ScanSettings scanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC).build();
+ performBleScan(scanSettings, false);
+ }
+
+ @Test
+ public void testBleScanUnoptimized() {
+ ScanSettings scanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+ performBleScan(scanSettings, false);
+ }
+
+ @Test
+ public void testBleScanResult() {
+ ScanSettings scanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+ // TODO: Add an extremely weak ScanFilter to allow background ble scan results.
+ performBleScan(scanSettings, true);
+ }
+
+ private static void performBleScan(ScanSettings scanSettings, boolean waitForResult) {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null) {
+ Log.e(TAG, "Device does not support Bluetooth");
+ return;
+ }
+ boolean bluetoothEnabledByTest = false;
+ if (!bluetoothAdapter.isEnabled()) {
+ if (!bluetoothAdapter.enable()) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ return;
+ }
+ sleep(8_000);
+ bluetoothEnabledByTest = true;
+ }
+ BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
+ if (bleScanner == null) {
+ Log.e(TAG, "Cannot access BLE scanner");
+ return;
+ }
+
+ CountDownLatch resultsLatch = new CountDownLatch(1);
+ ScanCallback scanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ Log.v(TAG, "called onScanResult");
+ resultsLatch.countDown();
+ }
+ @Override
+ public void onScanFailed(int errorCode) {
+ Log.v(TAG, "called onScanFailed");
+ }
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {
+ Log.v(TAG, "called onBatchScanResults");
+ resultsLatch.countDown();
+ }
+ };
+
+ bleScanner.startScan(null, scanSettings, scanCallback);
+ if (waitForResult) {
+ waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
+ } else {
+ sleep(2_000);
+ }
+ bleScanner.stopScan(scanCallback);
+
+ // Restore adapter state at end of test
+ if (bluetoothEnabledByTest) {
+ bluetoothAdapter.disable();
+ }
+ }
+
+ @Test
+ public void testCameraState() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ CameraManager cam = context.getSystemService(CameraManager.class);
+ String[] cameraIds = cam.getCameraIdList();
+ if (cameraIds.length == 0) {
+ Log.e(TAG, "No camera found on device");
+ return;
+ }
+
+ CountDownLatch latch = new CountDownLatch(1);
+ final CameraDevice.StateCallback cb = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(CameraDevice cd) {
+ Log.i(TAG, "CameraDevice " + cd.getId() + " opened");
+ sleep(2_000);
+ cd.close();
+ }
+ @Override
+ public void onClosed(CameraDevice cd) {
+ latch.countDown();
+ Log.i(TAG, "CameraDevice " + cd.getId() + " closed");
+ }
+ @Override
+ public void onDisconnected(CameraDevice cd) {
+ Log.w(TAG, "CameraDevice " + cd.getId() + " disconnected");
+ }
+ @Override
+ public void onError(CameraDevice cd, int error) {
+ Log.e(TAG, "CameraDevice " + cd.getId() + "had error " + error);
+ }
+ };
+
+ HandlerThread handlerThread = new HandlerThread("br_handler_thread");
+ handlerThread.start();
+ Looper looper = handlerThread.getLooper();
+ Handler handler = new Handler(looper);
+
+ cam.openCamera(cameraIds[0], cb, handler);
+ waitForReceiver(context, 10_000, latch, null);
+ }
+
+ @Test
+ public void testFlashlight() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ CameraManager cam = context.getSystemService(CameraManager.class);
+ String[] cameraIds = cam.getCameraIdList();
+ boolean foundFlash = false;
+ for (int i = 0; i < cameraIds.length; i++) {
+ String id = cameraIds[i];
+ if(cam.getCameraCharacteristics(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) {
+ cam.setTorchMode(id, true);
+ sleep(500);
+ cam.setTorchMode(id, false);
+ foundFlash = true;
+ break;
+ }
+ }
+ if(!foundFlash) {
+ Log.e(TAG, "No flashlight found on device");
+ }
+ }
+
+ @Test
+ public void testGpsScan() {
+ Context context = InstrumentationRegistry.getContext();
+ final LocationManager locManager = context.getSystemService(LocationManager.class);
+ if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ Log.e(TAG, "GPS provider is not enabled");
+ return;
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+
+ final LocationListener locListener = new LocationListener() {
+ public void onLocationChanged(Location location) {
+ Log.v(TAG, "onLocationChanged: location has been obtained");
+ }
+ public void onProviderDisabled(String provider) {
+ Log.w(TAG, "onProviderDisabled " + provider);
+ }
+ public void onProviderEnabled(String provider) {
+ Log.w(TAG, "onProviderEnabled " + provider);
+ }
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ Log.w(TAG, "onStatusChanged " + provider + " " + status);
+ }
+ };
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ Looper.prepare();
+ locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 990, 0,
+ locListener);
+ sleep(1_000);
+ locManager.removeUpdates(locListener);
+ latch.countDown();
+ return null;
+ }
+ }.execute();
+
+ waitForReceiver(context, 59_000, latch, null);
+ }
+
+ @Test
+ public void testWakeupAlarm() {
+ Context context = InstrumentationRegistry.getContext();
+ String name = "android.cts.statsd.testWakeupAlarm";
+ CountDownLatch onReceiveLatch = new CountDownLatch(1);
+ BroadcastReceiver receiver =
+ registerReceiver(context, onReceiveLatch, new IntentFilter(name));
+ AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
+ PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), 0);
+ manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + 2_000, pintent);
+ waitForReceiver(context, 10_000, onReceiveLatch, receiver);
+ }
+
+ @Test
+ public void testWifiLock() {
+ Context context = InstrumentationRegistry.getContext();
+ WifiManager wm = context.getSystemService(WifiManager.class);
+ WifiManager.WifiLock lock = wm.createWifiLock("StatsdCTSWifiLock");
+ lock.acquire();
+ sleep(500);
+ lock.release();
+ }
+
+ @Test
+ /** Does two wifi scans. */
+ // TODO: Copied this from BatterystatsValidation but we probably don't need to wait for results.
+ public void testWifiScan() {
+ Context context = InstrumentationRegistry.getContext();
+ IntentFilter intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ // Sometimes a scan was already running (from a different uid), so the first scan doesn't
+ // start when requested. Therefore, additionally wait for whatever scan is currently running
+ // to finish, then request a scan again - at least one of these two scans should be
+ // attributed to this app.
+ for (int i = 0; i < 2; i++) {
+ CountDownLatch onReceiveLatch = new CountDownLatch(1);
+ BroadcastReceiver receiver = registerReceiver(context, onReceiveLatch, intentFilter);
+ context.getSystemService(WifiManager.class).startScan();
+ waitForReceiver(context, 60_000, onReceiveLatch, receiver);
+ }
+ }
+
+ // ------- Helper methods
+
+ /** Puts the current thread to sleep. */
+ private static void sleep(int millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted exception while sleeping", e);
+ }
+ }
+
+ /** Register receiver to determine when given action is complete. */
+ private static BroadcastReceiver registerReceiver(
+ Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter) {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onReceiveLatch.countDown();
+ }
+ };
+ // Run Broadcast receiver in a different thread since the main thread will wait.
+ HandlerThread handlerThread = new HandlerThread("br_handler_thread");
+ handlerThread.start();
+ Looper looper = handlerThread.getLooper();
+ Handler handler = new Handler(looper);
+ ctx.registerReceiver(receiver, intentFilter, null, handler);
+ return receiver;
+ }
+
+ /**
+ * Uses the receiver to wait until the action is complete. ctx and receiver may be null if no
+ * receiver is needed to be unregistered.
+ */
+ private static void waitForReceiver(Context ctx,
+ int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver) {
+ try {
+ boolean didFinish = latch.await(maxWaitTimeMs, TimeUnit.MILLISECONDS);
+ if (didFinish) {
+ Log.v(TAG, "Finished performing action");
+ } else {
+ // This is not necessarily a problem. If we just want to make sure a count was
+ // recorded for the request, it doesn't matter if the action actually finished.
+ Log.w(TAG, "Did not finish in specified time.");
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted exception while awaiting action to finish", e);
+ }
+ if (ctx != null && receiver != null) {
+ ctx.unregisterReceiver(receiver);
+ }
+ }
+}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
new file mode 100644
index 0000000..b15ec35
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.VideoView;
+
+public class VideoPlayerActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ VideoView videoView = (VideoView)findViewById(R.id.video_player_view);
+ videoView.setVideoPath("android.resource://" + getPackageName() + "/" + R.raw.colors_video);
+ videoView.start();
+ }
+}
+
+
+
diff --git a/hostsidetests/statsd/src/android/cts/statsd/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/AtomTestCase.java
new file mode 100644
index 0000000..cbb63ac
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/AtomTestCase.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsd;
+
+
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.Bucket;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.log.LogUtil;
+import com.google.common.io.Files;
+import com.sun.istack.internal.Nullable;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Base class for testing Statsd atoms.
+ * Validates reporting of statsd logging based on different events
+ */
+public class AtomTestCase extends BaseTestCase {
+
+ private static final String UPDATE_CONFIG_CMD = "cmd stats config update";
+ private static final String DUMP_REPORT_CMD = "cmd stats dump-report";
+ private static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
+ protected static final String CONFIG_UID = "1000";
+ protected static final long CONFIG_ID = "cts_config".hashCode();
+
+ protected static final int WAIT_TIME_SHORT = 500;
+ protected static final int WAIT_TIME_LONG = 2_000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // TODO: need to do these before running real test:
+ // 1. compile statsd and push to device
+ // 2. make sure StatsCompanionService and incidentd is running
+ // 3. start statsd
+ // These should go away once we have statsd properly set up.
+
+ // Uninstall to clear the history in case it's still on the device.
+ removeConfig(CONFIG_ID);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ removeConfig(CONFIG_ID);
+ super.tearDown();
+ }
+
+ /**
+ * Determines whether logcat indicates that incidentd fired since the given device date.
+ */
+ protected boolean didIncidentdFireSince(String date) throws Exception {
+ final String INCIDENTD_TAG = "incidentd";
+ final String INCIDENTD_STARTED_STRING = "reportIncident";
+ // TODO: Do something more robust than this in case of delayed logging.
+ Thread.sleep(1000);
+ String log = getLogcatSince(date, String.format(
+ "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
+ return log.contains(INCIDENTD_STARTED_STRING);
+ }
+
+ protected static StatsdConfig.Builder createConfigBuilder() {
+ return StatsdConfig.newBuilder().setId(CONFIG_ID);
+ }
+
+ protected void createAndUploadConfig(int atomTag) throws Exception {
+ StatsdConfig.Builder conf = createConfigBuilder();
+ addAtomEvent(conf, atomTag);
+ uploadConfig(conf);
+ }
+
+ protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
+ uploadConfig(config.build());
+ }
+
+ protected void uploadConfig(StatsdConfig config) throws Exception {
+ File configFile = File.createTempFile("statsdconfig", ".config");
+ configFile.deleteOnExit();
+ Files.write(config.toByteArray(), configFile);
+ String remotePath = "/data/" + configFile.getName();
+ getDevice().pushFile(configFile, remotePath);
+ getDevice().executeShellCommand(
+ String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD, CONFIG_UID,
+ String.valueOf(CONFIG_ID)));
+ getDevice().executeShellCommand("rm " + remotePath);
+ }
+
+ protected void removeConfig(long configId) throws Exception {
+ getDevice().executeShellCommand(
+ String.join(" ", REMOVE_CONFIG_CMD, CONFIG_UID, String.valueOf(configId)));
+ }
+
+ protected List<EventMetricData> getEventMetricDataList() throws Exception {
+ ConfigMetricsReportList reportList = getReportList();
+ assertTrue(reportList.getReportsCount() == 1);
+ ConfigMetricsReport report = reportList.getReports(0);
+
+ List<EventMetricData> data = new ArrayList<>();
+ for (StatsLogReport metric : report.getMetricsList()) {
+ data.addAll(metric.getEventMetrics().getDataList());
+ }
+ data.sort(Comparator.comparing(EventMetricData::getTimestampNanos));
+
+ LogUtil.CLog.d("Get EventMetricDataList as following:\n");
+ for (EventMetricData d : data) {
+ LogUtil.CLog.d("Atom at " + d.getTimestampNanos() + ":\n" + d.getAtom().toString());
+ }
+ return data;
+ }
+
+ protected List<Atom> getGaugeMetricDataList() throws Exception {
+ ConfigMetricsReportList reportList = getReportList();
+ assertTrue(reportList.getReportsCount() == 1);
+ // only config
+ ConfigMetricsReport report = reportList.getReports(0);
+
+ List<Atom> data = new ArrayList<>();
+ for (GaugeMetricData gaugeMetricData : report.getMetrics(0).getGaugeMetrics().getDataList()) {
+ data.add(gaugeMetricData.getBucketInfo(0).getAtom());
+ }
+
+ LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
+ for (Atom d : data) {
+ LogUtil.CLog.d("Atom:\n" + d.toString());
+ }
+ return data;
+ }
+
+ protected ConfigMetricsReportList getReportList() throws Exception {
+ ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
+ String.join(" ", DUMP_REPORT_CMD, CONFIG_UID, String.valueOf(CONFIG_ID), "--proto"));
+ return reportList;
+ }
+
+ /** Creates a FieldValueMatcher.Builder corresponding to the given key. */
+ protected static FieldValueMatcher.Builder createKvm(int key) {
+ return FieldValueMatcher.newBuilder().setField(key);
+ }
+
+ protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
+ addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
+ }
+
+ /**
+ * Adds an event to the config for an atom that matches the given key.
+ * @param conf configuration
+ * @param atomTag atom tag (from atoms.proto)
+ * @param kvm FieldValueMatcher.Builder for the relevant key
+ */
+ protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder kvm)
+ throws Exception {
+ addAtomEvent(conf, atomTag, Arrays.asList(kvm));
+ }
+
+ /**
+ * Adds an event to the config for an atom that matches the given keys.
+ * @param conf configuration
+ * @param atomId atom tag (from atoms.proto)
+ * @param kvms list of FieldValueMatcher.Builders to attach to the atom. May be null.
+ */
+ protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
+ List<FieldValueMatcher.Builder> kvms) throws Exception {
+
+ final String atomName = "Atom" + System.nanoTime();
+ final String eventName = "Event" + System.nanoTime();
+
+ SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+ if (kvms != null) {
+ for (FieldValueMatcher.Builder kvm : kvms) {
+ sam.addFieldValueMatcher(kvm);
+ }
+ }
+ conf.addAtomMatcher(AtomMatcher.newBuilder()
+ .setId(atomName.hashCode())
+ .setSimpleAtomMatcher(sam));
+ conf.addEventMetric(EventMetric.newBuilder()
+ .setId(eventName.hashCode())
+ .setWhat(atomName.hashCode()));
+ }
+
+ /**
+ * Adds an atom to a gauge metric of a config
+ * @param conf configuration
+ * @param atomId atom id (from atoms.proto)
+ * @param dimension dimension is needed for most pulled atoms
+ */
+ protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId, @Nullable FieldMatcher.Builder dimension) throws Exception {
+ final String atomName = "Atom" + System.nanoTime();
+ final String gaugeName = "Gauge" + + System.nanoTime();
+ final String predicateName = "SCREEN_IS_ON";
+ SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+ conf.addAtomMatcher(AtomMatcher.newBuilder()
+ .setId(atomName.hashCode())
+ .setSimpleAtomMatcher(sam));
+ // TODO: change this predicate to something simpler and easier
+ final String predicateTrueName = "SCREEN_TURNED_ON";
+ final String predicateFalseName = "SCREEN_TURNED_OFF";
+ conf.addAtomMatcher(AtomMatcher.newBuilder()
+ .setId(predicateTrueName.hashCode())
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+ .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+ .setField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)
+ .setEqInt(ScreenStateChanged.State.STATE_ON_VALUE)
+ )
+ )
+ )
+ // Used to trigger predicate
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setId(predicateFalseName.hashCode())
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+ .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+ .setField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)
+ .setEqInt(ScreenStateChanged.State.STATE_OFF_VALUE)
+ )
+ )
+ );
+ conf.addPredicate(Predicate.newBuilder()
+ .setId(predicateName.hashCode())
+ .setSimplePredicate(SimplePredicate.newBuilder()
+ .setStart(predicateTrueName.hashCode())
+ .setStop(predicateFalseName.hashCode())
+ .setCountNesting(false)
+ )
+ );
+ GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+ .setId(gaugeName.hashCode())
+ .setWhat(atomName.hashCode())
+ .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+ .setBucket(TimeUnit.CTS)
+ .setCondition(predicateName.hashCode());
+ if (dimension != null) {
+ gaugeMetric.setDimensions(dimension.build());
+ }
+ conf.addGaugeMetric(gaugeMetric.build());
+ }
+
+ /**
+ * Asserts that each set of states in stateSets occurs at least once in data.
+ * Asserts that the states in data occur in the same order as the sets in stateSets.
+ * @param stateSets A list of set of states, where each set represents an equivalent state of
+ * the device for the purpose of CTS.
+ * @param data list of EventMetricData from statsd, produced by getReportMetricListData()
+ * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+ */
+ public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
+ int wait, Function<Atom, Integer> getStateFromAtom) {
+ // Sometimes, there are more events than there are states.
+ // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+ assertTrue(data.size() >= stateSets.size());
+ int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
+ for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
+ Atom atom = data.get(dataIndex).getAtom();
+ int state = getStateFromAtom.apply(atom);
+ // If state is in the current state set, we do not assert anything.
+ // If it is not, we expect to have transitioned to the next state set.
+ if (!stateSets.get(stateSetIndex).contains(state)) {
+ stateSetIndex += 1;
+ LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+ + " in stateSetIndex " + stateSetIndex + ":\n"
+ + data.get(dataIndex).getAtom().toString());
+ assertTrue(dataIndex != 0); // We shoud not be on the first data.
+ assertTrue(stateSetIndex < stateSets.size()); // Out of bounds check.
+ assertTrue(stateSets.get(stateSetIndex).contains(state));
+ long diffMs = (data.get(dataIndex).getTimestampNanos() -
+ data.get(dataIndex - 1).getTimestampNanos()) / 1_000_000;
+ assertTrue(wait / 2 < diffMs);
+ assertTrue(wait * 5 > diffMs);
+ }
+ }
+ assertTrue(stateSetIndex == stateSets.size() - 1); // We saw each state set.
+ }
+
+ protected void turnScreenOn() throws Exception {
+ getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ getDevice().executeShellCommand("wm dismiss-keyguard");
+ }
+
+ protected void turnScreenOff() throws Exception {
+ getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+ }
+
+ protected void setChargingState(int state) throws Exception {
+ getDevice().executeShellCommand("cmd battery set status " + state);
+ }
+
+ protected void unplugDevice() throws Exception {
+ getDevice().executeShellCommand("cmd battery unplug");
+ }
+
+ protected void plugInAc() throws Exception {
+ getDevice().executeShellCommand("cmd battery set ac 1");
+ }
+
+ protected void plugInUsb() throws Exception {
+ getDevice().executeShellCommand("cmd battery set usb 1");
+ }
+
+ protected void plugInWireless() throws Exception {
+ getDevice().executeShellCommand("cmd battery set wireless 1");
+ }
+
+ protected void setBatteryLevel(int level) throws Exception {
+ getDevice().executeShellCommand("cmd battery set level " + level);
+ }
+
+ protected void resetBatteryStatus() throws Exception {
+ getDevice().executeShellCommand("cmd battery reset");
+ }
+
+ protected int getScreenBrightness() throws Exception {
+ return Integer.parseInt(
+ getDevice().executeShellCommand("settings get system screen_brightness").trim());
+ }
+
+ protected void setScreenBrightness(int brightness) throws Exception {
+ getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
+ }
+
+ protected boolean isScreenBrightnessModeManual() throws Exception {
+ String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
+ return Integer.parseInt(mode.trim()) == 0;
+ }
+
+ protected void setScreenBrightnessMode(boolean manual) throws Exception {
+ getDevice().executeShellCommand(
+ "settings put system screen_brightness_mode " + (manual? 0 : 1));
+ }
+
+ protected int getScreenTimeoutMs() throws Exception {
+ return Integer.parseInt(
+ getDevice().executeShellCommand("settings get system screen_off_timeout").trim());
+ }
+ protected void setScreenTimeoutMs(int timeout) throws Exception {
+ getDevice().executeShellCommand("settings put system screen_off_timeout " + timeout);
+ }
+
+ protected void enterDozeModeLight() throws Exception {
+ getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
+ }
+
+ protected void enterDozeModeDeep() throws Exception {
+ getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
+ }
+ protected void leaveDozeMode() throws Exception {
+ getDevice().executeShellCommand("dumpsys deviceidle unforce");
+ getDevice().executeShellCommand("dumpsys deviceidle disable");
+ getDevice().executeShellCommand("dumpsys deviceidle enable");
+ }
+
+ protected void turnBatterySaverOn() throws Exception {
+ getDevice().executeShellCommand("cmd battery unplug");
+ getDevice().executeShellCommand("settings put global low_power 1");
+ }
+
+ protected void turnBatterySaverOff() throws Exception {
+ getDevice().executeShellCommand("settings put global low_power 0");
+ getDevice().executeShellCommand("cmd battery reset");
+ }
+
+ protected void rebootDevice() throws Exception {
+ getDevice().rebootUntilOnline();
+ }
+
+ /**
+ * Determines whether the two events are within the specified range of each other.
+ * @param d0 the event that should occur first
+ * @param d1 the event that should occur second
+ * @param minDiffMs d0 should precede d1 by at least this amount
+ * @param maxDiffMs d0 should precede d1 by at most this amount
+ * @return
+ */
+ public static boolean isTimeDiffBetween(EventMetricData d0, EventMetricData d1,
+ int minDiffMs, int maxDiffMs) {
+ long diffMs = (d1.getTimestampNanos() - d0.getTimestampNanos()) / 1_000_000;
+ return minDiffMs <= diffMs && diffMs <= maxDiffMs;
+ }
+
+ protected String getCurrentLogcatDate() throws Exception {
+ // TODO: Do something more robust than this for getting logcat markers.
+ long timestampMs = getDevice().getDeviceDate();
+ return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+ .format(new Date(timestampMs));
+ }
+
+ protected String getLogcatSince(String date, String logcatParams) throws Exception {
+ return getDevice().executeShellCommand(String.format(
+ "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+ }
+
+ /**
+ * Determines if the device has the given feature.
+ * Prints a warning if its value differs from requiredAnswer.
+ */
+ protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
+ final String features = getDevice().executeShellCommand("pm list features");
+ boolean hasIt = features.contains(featureName);
+ if (hasIt != requiredAnswer) {
+ LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
+ + featureName);
+ }
+ return hasIt;
+ }
+
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/src/android/cts/statsd/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/BaseTestCase.java
new file mode 100644
index 0000000..31eec47
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/BaseTestCase.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsd;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+// Largely copied from incident's ProtoDumpTestCase
+public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
+
+ protected IBuildInfo mCtsBuild;
+
+ private static final String TEST_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertNotNull(mCtsBuild);
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ /**
+ * Call onto the device with an adb shell command and get the results of
+ * that as a proto of the given type.
+ *
+ * @param parser A protobuf parser object. e.g. MyProto.parser()
+ * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
+ *
+ * @throws DeviceNotAvailableException If there was a problem communicating with
+ * the test device.
+ * @throws InvalidProtocolBufferException If there was an error parsing
+ * the proto. Note that a 0 length buffer is not necessarily an error.
+ */
+ public <T extends MessageLite> T getDump(Parser<T> parser, String command)
+ throws DeviceNotAvailableException, InvalidProtocolBufferException {
+ final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+ getDevice().executeShellCommand(command, receiver);
+ return parser.parseFrom(receiver.getOutput());
+ }
+
+ /**
+ * Install a device side test package.
+ *
+ * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+ * @param grantPermissions whether to give runtime permissions.
+ */
+ protected void installPackage(String appFileName, boolean grantPermissions)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ CLog.d("Installing app " + appFileName);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ final String result = getDevice().installPackage(
+ buildHelper.getTestFile(appFileName), true, grantPermissions);
+ assertNull("Failed to install " + appFileName + ": " + result, result);
+ }
+
+ /**
+ * Run a device side test.
+ *
+ * @param pkgName Test package name, such as "com.android.server.cts.netstats".
+ * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
+ * @param testMethodName Test method name.
+ * @throws DeviceNotAvailableException
+ */
+ protected void runDeviceTests(@Nonnull String pkgName,
+ @Nullable String testClassName, @Nullable String testMethodName)
+ throws DeviceNotAvailableException {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+ pkgName, TEST_RUNNER, getDevice().getIDevice());
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ CollectingTestListener listener = new CollectingTestListener();
+ assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+
+ final TestRunResult result = listener.getCurrentRunResults();
+ if (result.isRunFailure()) {
+ throw new AssertionError("Failed to successfully run device tests for "
+ + result.getName() + ": " + result.getRunFailureMessage());
+ }
+ if (result.getNumTests() == 0) {
+ throw new AssertionError("No tests were run on the device");
+ }
+
+ if (result.hasFailedTests()) {
+ // build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+ for (Map.Entry<TestIdentifier, TestResult> resultEntry :
+ result.getTestResults().entrySet()) {
+ if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+ errorBuilder.append(resultEntry.getKey().toString());
+ errorBuilder.append(":\n");
+ errorBuilder.append(resultEntry.getValue().getStackTrace());
+ }
+ }
+ throw new AssertionError(errorBuilder.toString());
+ }
+ }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/DeviceAtomTestCase.java
new file mode 100644
index 0000000..7c49407
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/DeviceAtomTestCase.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsd;
+
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
+ */
+public class DeviceAtomTestCase extends AtomTestCase {
+
+ protected static final String DEVICE_SIDE_TEST_APK = "CtsStatsdAtomsApp.apk";
+ protected static final String DEVICE_SIDE_TEST_PACKAGE
+ = "com.android.server.cts.device.statsd";
+
+ protected static final String CONFIG_NAME = "cts_config";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ installTestApp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ super.tearDown();
+ }
+
+ /**
+ * Performs a device-side test by calling a method on the app and returns its stats events.
+ * @param methodName the name of the method in the app's AtomTests to perform
+ * @param atom atom tag (from atoms.proto)
+ * @param key atom's field corresponding to state
+ * @param stateOn 'on' value
+ * @param stateOff 'off' value
+ * @param minTimeDiffMs max allowed time between start and stop
+ * @param maxTimeDiffMs min allowed time between start and stop
+ * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
+ * @return list of events with the app's uid matching the configuration defined by the params.
+ */
+ protected List<EventMetricData> doDeviceMethodOnOff(
+ String methodName, int atom, int key, int stateOn, int stateOff,
+ int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
+ StatsdConfig.Builder conf = createConfigBuilder();
+ addAtomEvent(conf, atom, createKvm(key).setEqInt(stateOn));
+ addAtomEvent(conf, atom, createKvm(key).setEqInt(stateOff));
+ List<EventMetricData> data = doDeviceMethod(methodName, conf);
+
+ if (demandExactlyTwo) {
+ assertTrue(data.size() == 2);
+ } else {
+ assertTrue(data.size() >= 2);
+ }
+ assertTrue(isTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs));
+ return data;
+ }
+
+ /**
+ *
+ * @param methodName the name of the method in the app's AtomTests to perform
+ * @param cfg statsd configuration
+ * @return list of events with the app's uid matching the configuration.
+ */
+ protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
+ throws Exception {
+ removeConfig(CONFIG_ID);
+ uploadConfig(cfg);
+ int appUid = getUid();
+ LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
+
+ return getEventMetricDataList();
+ }
+
+ /**
+ * Adds an event to the config for an atom that matches the given key AND has the app's uid.
+ * @param conf configuration
+ * @param atomTag atom tag (from atoms.proto)
+ * @param kvm FieldValueMatcher.Builder for the relevant key
+ */
+ @Override
+ protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder kvm)
+ throws Exception {
+
+ final int UID_KEY = 1;
+ FieldValueMatcher.Builder kvmUid = createKvm(UID_KEY).setEqInt(getUid());
+ addAtomEvent(conf, atomTag, Arrays.asList(kvm, kvmUid));
+ }
+
+ /**
+ * Adds an event to the config for an atom that matches the app's uid.
+ * @param conf configuration
+ * @param atomTag atom tag (from atoms.proto)
+ */
+ @Override
+ protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
+ final int UID_KEY = 1;
+ FieldValueMatcher.Builder kvmUid = createKvm(UID_KEY).setEqInt(getUid());
+ addAtomEvent(conf, atomTag, Arrays.asList(kvmUid));
+ }
+
+ /**
+ * Gets the uid of the test app.
+ */
+ protected int getUid() throws Exception {
+ String uidLine = getDevice().executeShellCommand("cmd package list packages -U "
+ + DEVICE_SIDE_TEST_PACKAGE);
+ String[] uidLineParts = uidLine.split(":");
+ // 3rd entry is package uid
+ assertTrue(uidLineParts.length > 2);
+ int uid = Integer.parseInt(uidLineParts[2].trim());
+ assertTrue(uid > 10000);
+ return uid;
+ }
+
+ /**
+ * Installs the test apk.
+ */
+ protected void installTestApp() throws Exception {
+ installPackage(DEVICE_SIDE_TEST_APK, true);
+ allowBackgroundServices();
+ }
+
+ /**
+ * Required to successfully start a background service from adb in O.
+ */
+ protected void allowBackgroundServices() throws Exception {
+ getDevice().executeShellCommand(String.format(
+ "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
+ }
+}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/HostAtomTests.java
new file mode 100644
index 0000000..5ef6deb
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/HostAtomTests.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsd;
+
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
+import com.android.internal.os.StatsdConfigProto.Bucket;
+import com.android.internal.os.StatsdConfigProto.CountMetric;
+import com.android.internal.os.StatsdConfigProto.DurationMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.internal.os.StatsdConfigProto.ValueMetric;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.BatteryLevelChanged;
+import com.android.os.AtomsProto.BatterySaverModeStateChanged;
+import com.android.os.AtomsProto.ChargingStateChanged;
+import com.android.os.AtomsProto.CpuTimePerUid;
+import com.android.os.AtomsProto.CpuTimePerFreq;
+import com.android.os.AtomsProto.CpuTimePerUidFreq;
+import com.android.os.AtomsProto.DeviceIdleModeStateChanged;
+import com.android.os.AtomsProto.KernelWakelock;
+import com.android.os.AtomsProto.PlatformSleepState;
+import com.android.os.AtomsProto.PluggedStateChanged;
+import com.android.os.AtomsProto.ScreenBrightnessChanged;
+import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.AtomsProto.SleepStateVoter;
+import com.android.os.AtomsProto.SubsystemSleepState;
+import com.android.os.StatsLog.EventMetricData;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Statsd atom tests that are done via adb (hostside).
+ */
+public class HostAtomTests extends AtomTestCase {
+
+ private static final String TAG = "Statsd.HostAtomTests";
+
+ private static final boolean TESTS_ENABLED = false;
+ // For tests that require incidentd. Keep as true until TESTS_ENABLED is permanently enabled.
+ private static final boolean INCIDENTD_TESTS_ENABLED = false;
+
+ private static final long TEST_CONFIG_ID = "cts_test_config".hashCode();
+
+ public void testScreenStateChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup, make sure the screen is off.
+ turnScreenOff();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> screenOnStates = new HashSet<>(
+ Arrays.asList(ScreenStateChanged.State.STATE_ON_VALUE,
+ ScreenStateChanged.State.STATE_ON_SUSPEND_VALUE,
+ ScreenStateChanged.State.STATE_VR_VALUE));
+ Set<Integer> screenOffStates = new HashSet<>(
+ Arrays.asList(ScreenStateChanged.State.STATE_OFF_VALUE,
+ ScreenStateChanged.State.STATE_DOZE_VALUE,
+ ScreenStateChanged.State.STATE_DOZE_SUSPEND_VALUE,
+ ScreenStateChanged.State.STATE_UNKNOWN_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(screenOnStates, screenOffStates);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOff();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+ atom -> atom.getScreenStateChanged().getDisplayState().getNumber());
+ }
+
+ public void testChargingStateChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup, set charging state to full.
+ setChargingState(5);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.CHARGING_STATE_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> batteryUnknownStates = new HashSet<>(
+ Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_UNKNOWN_VALUE));
+ Set<Integer> batteryChargingStates = new HashSet<>(
+ Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_CHARGING_VALUE));
+ Set<Integer> batteryDischargingStates = new HashSet<>(
+ Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_DISCHARGING_VALUE));
+ Set<Integer> batteryNotChargingStates = new HashSet<>(
+ Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_NOT_CHARGING_VALUE));
+ Set<Integer> batteryFullStates = new HashSet<>(
+ Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_FULL_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(batteryUnknownStates, batteryChargingStates,
+ batteryDischargingStates, batteryNotChargingStates, batteryFullStates);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ setChargingState(1);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setChargingState(2);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setChargingState(3);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setChargingState(4);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setChargingState(5);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Unfreeze battery state after test
+ resetBatteryStatus();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getChargingStateChanged().getChargingState().getNumber());
+ }
+
+ public void testPluggedStateChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup, unplug device.
+ unplugDevice();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> unpluggedStates = new HashSet<>(
+ Arrays.asList(PluggedStateChanged.State.BATTERY_PLUGGED_NONE_VALUE));
+ Set<Integer> acStates = new HashSet<>(
+ Arrays.asList(PluggedStateChanged.State.BATTERY_PLUGGED_AC_VALUE));
+ Set<Integer> usbStates = new HashSet<>(
+ Arrays.asList(PluggedStateChanged.State.BATTERY_PLUGGED_USB_VALUE));
+ Set<Integer> wirelessStates = new HashSet<>(
+ Arrays.asList(PluggedStateChanged.State.BATTERY_PLUGGED_WIRELESS_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(acStates, unpluggedStates, usbStates,
+ unpluggedStates, wirelessStates, unpluggedStates);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ plugInAc();
+ Thread.sleep(WAIT_TIME_SHORT);
+ unplugDevice();
+ Thread.sleep(WAIT_TIME_SHORT);
+ plugInUsb();
+ Thread.sleep(WAIT_TIME_SHORT);
+ unplugDevice();
+ Thread.sleep(WAIT_TIME_SHORT);
+ plugInWireless();
+ Thread.sleep(WAIT_TIME_SHORT);
+ unplugDevice();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Unfreeze battery state after test
+ resetBatteryStatus();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getPluggedStateChanged().getPluggedState().getNumber());
+ }
+
+ public void testBatteryLevelChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup, set battery level to full.
+ setBatteryLevel(100);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> batteryDead = new HashSet<>(Arrays.asList(0));
+ Set<Integer> battery25p = new HashSet<>(Arrays.asList(25));
+ Set<Integer> battery50p = new HashSet<>(Arrays.asList(50));
+ Set<Integer> battery75p = new HashSet<>(Arrays.asList(75));
+ Set<Integer> batteryFull = new HashSet<>(Arrays.asList(100));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(batteryDead, battery25p, battery50p,
+ battery75p, batteryFull);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ setBatteryLevel(0);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setBatteryLevel(25);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setBatteryLevel(50);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setBatteryLevel(75);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setBatteryLevel(100);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Unfreeze battery state after test
+ resetBatteryStatus();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getBatteryLevelChanged().getBatteryLevel());
+ }
+
+ public void testScreenBrightnessChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup: record initial brightness state, set mode to manual and brightness to full.
+ int initialBrightness = getScreenBrightness();
+ boolean isInitialManual = isScreenBrightnessModeManual();
+ int initialTimeout = getScreenTimeoutMs();
+ setScreenTimeoutMs(600000);
+ turnScreenOn();
+ setScreenBrightnessMode(true);
+ setScreenBrightness(255);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.SCREEN_BRIGHTNESS_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> screenMin = new HashSet<>(Arrays.asList(25));
+ Set<Integer> screen100 = new HashSet<>(Arrays.asList(100));
+ Set<Integer> screen200 = new HashSet<>(Arrays.asList(200));
+ Set<Integer> screenMax = new HashSet<>(Arrays.asList(255));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100, screen200, screenMax);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ setScreenBrightness(25);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setScreenBrightness(100);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setScreenBrightness(200);
+ Thread.sleep(WAIT_TIME_SHORT);
+ setScreenBrightness(255);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Restore initial screen brightness
+ setScreenBrightness(initialBrightness);
+ setScreenBrightnessMode(isInitialManual);
+ setScreenTimeoutMs(initialTimeout);
+ turnScreenOff();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getScreenBrightnessChanged().getLevel());
+ }
+
+ public void testDeviceIdleModeStateChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup, leave doze mode.
+ leaveDozeMode();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.DEVICE_IDLE_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> dozeOff = new HashSet<>(
+ Arrays.asList(DeviceIdleModeStateChanged.State.DEVICE_IDLE_MODE_OFF_VALUE));
+ Set<Integer> dozeLight = new HashSet<>(
+ Arrays.asList(DeviceIdleModeStateChanged.State.DEVICE_IDLE_MODE_LIGHT_VALUE));
+ Set<Integer> dozeDeep = new HashSet<>(
+ Arrays.asList(DeviceIdleModeStateChanged.State.DEVICE_IDLE_MODE_DEEP_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(dozeLight, dozeDeep, dozeOff);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ enterDozeModeLight();
+ Thread.sleep(WAIT_TIME_SHORT);
+ enterDozeModeDeep();
+ Thread.sleep(WAIT_TIME_SHORT);
+ leaveDozeMode();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();;
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
+ }
+
+ public void testBatterySaverModeStateChangedAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+
+ // Setup, turn off battery saver.
+ turnBatterySaverOff();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ final int atomTag = Atom.BATTERY_SAVER_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> batterySaverOn = new HashSet<>(
+ Arrays.asList(BatterySaverModeStateChanged.State.ON_VALUE));
+ Set<Integer> batterySaverOff = new HashSet<>(
+ Arrays.asList(BatterySaverModeStateChanged.State.OFF_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(batterySaverOn, batterySaverOff);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Trigger events in same order.
+ turnBatterySaverOn();
+ Thread.sleep(WAIT_TIME_SHORT);
+ turnBatterySaverOff();
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
+ }
+
+ // TODO: Anomaly detection will be moved to general statsd device-side tests.
+ // Tests that anomaly detection for count works.
+ // Also tests that anomaly detection works when spanning multiple buckets.
+ public void testCountAnomalyDetection() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!INCIDENTD_TESTS_ENABLED) return;
+ // TODO: Don't use screen-state as the atom.
+ StatsdConfig config = getPulledAndAnomalyConfig()
+ .addCountMetric(CountMetric.newBuilder()
+ .setId("METRIC".hashCode())
+ .setWhat("SCREEN_TURNED_ON".hashCode())
+ .setBucket(TimeUnit.CTS)
+ )
+ .addAlert(Alert.newBuilder()
+ .setId("testCountAnomalyDetectionAlert".hashCode())
+ .setMetricId("METRIC".hashCode())
+ .setNumBuckets(4)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(2)
+ )
+ .addSubscription(Subscription.newBuilder()
+ .setId("AlertSub".hashCode())
+ .setRuleType(Subscription.RuleType.ALERT)
+ .setRuleId("testCountAnomalyDetectionAlert".hashCode())
+ .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+ )
+ .build();
+ uploadConfig(config);
+
+ String markDeviceDate = getCurrentLogcatDate();
+ turnScreenOn(); // count -> 1 (not an anomaly, since not "greater than 2")
+ Thread.sleep(1000);
+ turnScreenOff();
+ Thread.sleep(3000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+
+ turnScreenOn(); // count ->2 (not an anomaly, since not "greater than 2")
+ Thread.sleep(1000);
+ turnScreenOff();
+ Thread.sleep(1000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+
+ turnScreenOn(); // count ->3 (anomaly, since "greater than 2"!)
+ Thread.sleep(1000);
+ assertTrue(didIncidentdFireSince(markDeviceDate));
+
+ turnScreenOff();
+ }
+
+ // Tests that anomaly detection for duration works.
+ // Also tests that refractory periods in anomaly detection work.
+ public void testDurationAnomalyDetection() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!INCIDENTD_TESTS_ENABLED) return;
+ // TODO: Do NOT use screenState for this, since screens auto-turn-off after a variable time.
+ StatsdConfig config = getPulledAndAnomalyConfig()
+ .addDurationMetric(DurationMetric.newBuilder()
+ .setId("METRIC".hashCode())
+ .setWhat("SCREEN_IS_ON".hashCode())
+ .setAggregationType(DurationMetric.AggregationType.SUM)
+ .setBucket(TimeUnit.CTS)
+ )
+ .addAlert(Alert.newBuilder()
+ .setId("testDurationAnomalyDetectionAlert".hashCode())
+ .setMetricId("METRIC".hashCode())
+ .setNumBuckets(12)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(15_000_000_000L) // 15 seconds in nanoseconds
+ )
+ .addSubscription(Subscription.newBuilder()
+ .setId("AlertSub".hashCode())
+ .setRuleType(Subscription.RuleType.ALERT)
+ .setRuleId("testDurationAnomalyDetectionAlert".hashCode())
+ .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+ )
+ .build();
+ uploadConfig(config);
+
+ // Test that alarm doesn't fire early.
+ String markDeviceDate = getCurrentLogcatDate();
+ turnScreenOn();
+ Thread.sleep(6_000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+
+ turnScreenOff();
+ Thread.sleep(1_000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+
+ // Test that alarm does fire when it is supposed to.
+ turnScreenOn();
+ Thread.sleep(13_000);
+ assertTrue(didIncidentdFireSince(markDeviceDate));
+
+ // Now test that the refractory period is obeyed.
+ markDeviceDate = getCurrentLogcatDate();
+ turnScreenOff();
+ Thread.sleep(1_000);
+ turnScreenOn();
+ Thread.sleep(1_000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+
+ // Test that detection works again after refractory period finishes.
+ turnScreenOff();
+ Thread.sleep(20_000);
+ turnScreenOn();
+ Thread.sleep(15_000);
+ assertTrue(didIncidentdFireSince(markDeviceDate));
+ }
+
+ // TODO: There is no value anomaly detection code yet! So this will fail.
+ // Tests that anomaly detection for value works.
+ public void testValueAnomalyDetection() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!INCIDENTD_TESTS_ENABLED) return;
+ // TODO: Definitely don't use screen-state as the atom. This MUST be changed.
+ StatsdConfig config = getPulledAndAnomalyConfig()
+ .addValueMetric(ValueMetric.newBuilder()
+ .setId("METRIC".hashCode())
+ .setWhat("SCREEN_TURNED_ON".hashCode())
+ .setValueField(FieldMatcher.newBuilder()
+ .setField(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)))
+ .setBucket(TimeUnit.CTS)
+ )
+ .addAlert(Alert.newBuilder()
+ .setId("testValueAnomalyDetectionAlert".hashCode())
+ .setMetricId("METRIC".hashCode())
+ .setNumBuckets(4)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(ScreenStateChanged.State.STATE_OFF.getNumber())
+ )
+ .addSubscription(Subscription.newBuilder()
+ .setId("AlertSub".hashCode())
+ .setRuleType(Subscription.RuleType.ALERT)
+ .setRuleId("testValueAnomalyDetectionAlert".hashCode())
+ .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+ )
+ .build();
+ uploadConfig(config);
+
+ turnScreenOff();
+ String markDeviceDate = getCurrentLogcatDate();
+ turnScreenOff(); // value = STATE_OFF = 1 (probably)
+ Thread.sleep(2000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+ turnScreenOn(); // value = STATE_ON = 2 (probably)
+ Thread.sleep(2000);
+ assertTrue(didIncidentdFireSince(markDeviceDate));
+
+ turnScreenOff();
+ }
+
+ // Tests that anomaly detection for gauge works.
+ public void testGaugeAnomalyDetection() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!INCIDENTD_TESTS_ENABLED) return;
+ // TODO: Definitely don't use screen-state as the atom. This MUST be changed.
+ StatsdConfig config = getPulledAndAnomalyConfig()
+ .addGaugeMetric(GaugeMetric.newBuilder()
+ .setId("METRIC".hashCode())
+ .setWhat("SCREEN_TURNED_ON".hashCode())
+ .setGaugeFieldsFilter(
+ FieldFilter.newBuilder()
+ .setFields(FieldMatcher.newBuilder()
+ .setField(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER))
+ ))
+ .setBucket(TimeUnit.CTS)
+ )
+ .addAlert(Alert.newBuilder()
+ .setId("testGaugeAnomalyDetectionAlert".hashCode())
+ .setMetricId("METRIC".hashCode())
+ .setNumBuckets(1)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(ScreenStateChanged.State.STATE_OFF.getNumber())
+ )
+ .addSubscription(Subscription.newBuilder()
+ .setId("AlertSub".hashCode())
+ .setRuleType(Subscription.RuleType.ALERT)
+ .setRuleId("testGaugeAnomalyDetectionAlert".hashCode())
+ .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+ )
+ .build();
+ uploadConfig(config);
+
+ turnScreenOff();
+ String markDeviceDate = getCurrentLogcatDate();
+ turnScreenOff(); // gauge = STATE_OFF = 1 (probably)
+ Thread.sleep(2000);
+ assertFalse(didIncidentdFireSince(markDeviceDate));
+ turnScreenOn(); // gauge = STATE_ON = 2 (probably)
+ Thread.sleep(2000);
+ assertTrue(didIncidentdFireSince(markDeviceDate));
+
+ turnScreenOff();
+ }
+
+ public void testKernelWakelock() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.KERNEL_WAKELOCK_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(KernelWakelock.NAME_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.KERNEL_WAKELOCK_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(!atom.getKernelWakelock().getName().equals(""));
+ assertTrue(atom.getKernelWakelock().hasCount());
+ assertTrue(atom.getKernelWakelock().hasVersion());
+ assertTrue(atom.getKernelWakelock().getVersion() > 0);
+ assertTrue(atom.getKernelWakelock().hasTime());
+ }
+
+ public void testCpuTimePerFreq() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.CPU_TIME_PER_FREQ_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(CpuTimePerFreq.CLUSTER_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.CPU_TIME_PER_FREQ_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(2000);
+ turnScreenOn();
+ Thread.sleep(2000);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(atom.getCpuTimePerFreq().getCluster() >= 0);
+ assertTrue(atom.getCpuTimePerFreq().getFreqIndex() >= 0);
+ assertTrue(atom.getCpuTimePerFreq().getTimeMs() > 0);
+ }
+
+ public void testCpuTimePerUidFreq() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.CPU_TIME_PER_UID_FREQ_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(CpuTimePerUidFreq.UID_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.CPU_TIME_PER_UID_FREQ_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(atom.getCpuTimePerUidFreq().getUid() > 0);
+ assertTrue(atom.getCpuTimePerUidFreq().getFreqIdx() >= 0);
+ assertTrue(atom.getCpuTimePerUidFreq().getTimeMs() > 0);
+ }
+
+ public void testCpuTimePerUid() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.CPU_TIME_PER_UID_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(CpuTimePerUid.UID_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.CPU_TIME_PER_UID_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(atom.getCpuTimePerUid().getUid() > 0);
+ assertTrue(atom.getCpuTimePerUid().getUserTimeMs() > 0);
+ assertTrue(atom.getCpuTimePerUid().getSysTimeMs() > 0);
+ }
+
+ public void testPlatformSleepState() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.PLATFORM_SLEEP_STATE_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(PlatformSleepState.NAME_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.PLATFORM_SLEEP_STATE_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(!atom.getPlatformSleepState().getName().equals(""));
+ }
+
+ public void testSleepStateVoter() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.SLEEP_STATE_VOTER_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(SleepStateVoter.VOTER_NAME_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.SLEEP_STATE_VOTER_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(!atom.getSleepStateVoter().getPlatformSleepStateName().equals(""));
+ assertTrue(!atom.getSleepStateVoter().getVoterName().equals(""));
+ }
+
+ public void testSubsystemSleepState() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+ FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+ .setField(Atom.SUBSYSTEM_SLEEP_STATE_FIELD_NUMBER)
+ .addChild(FieldMatcher.newBuilder()
+ .setField(SubsystemSleepState.SUBSYSTEM_NAME_FIELD_NUMBER));
+ addGaugeAtom(config, Atom.SUBSYSTEM_SLEEP_STATE_FIELD_NUMBER, dimension);
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(WAIT_TIME_LONG);
+ turnScreenOn();
+ Thread.sleep(WAIT_TIME_LONG);
+
+ List<Atom> data = getGaugeMetricDataList();
+
+ Atom atom = data.get(0);
+ assertTrue(!atom.getSubsystemSleepState().getSubsystemName().equals(""));
+ assertTrue(!atom.getSubsystemSleepState().getSubsystemSleepStateName().equals(""));
+ }
+
+ /**
+ * TODO: Anomaly detection will be moved to general statsd device-side tests.
+ * Pulled atoms also should have a better way of constructing the config.
+ * Remove this config when that happens.
+ */
+ protected StatsdConfig.Builder getPulledAndAnomalyConfig() {
+ return StatsdConfig.newBuilder().setId(TEST_CONFIG_ID);
+ }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/src/android/cts/statsd/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/UidAtomTests.java
new file mode 100644
index 0000000..b0e9e5c
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/UidAtomTests.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsd;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.BleScanResultReceived;
+import com.android.os.AtomsProto.BleScanStateChanged;
+import com.android.os.AtomsProto.BleUnoptimizedScanStateChanged;
+import com.android.os.AtomsProto.CameraStateChanged;
+import com.android.os.AtomsProto.FlashlightStateChanged;
+import com.android.os.AtomsProto.WifiRadioPowerStateChanged;
+import com.android.os.AtomsProto.MobileRadioPowerStateChanged;
+import com.android.os.AtomsProto.AudioStateChanged;
+import com.android.os.AtomsProto.MediaCodecActivityChanged;
+import com.android.os.AtomsProto.GpsScanStateChanged;
+import com.android.os.AtomsProto.WakeupAlarmOccurred;
+import com.android.os.AtomsProto.WifiLockStateChanged;
+import com.android.os.AtomsProto.WifiScanStateChanged;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class UidAtomTests extends DeviceAtomTestCase {
+
+ private static final String TAG = "Statsd.UidAtomTests";
+
+ private static final boolean TESTS_ENABLED = false;
+
+ // These constants are those in PackageManager.
+ private static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+ private static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+ private static final String FEATURE_WIFI = "android.hardware.wifi";
+ private static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+ private static final String FEATURE_CAMERA = "android.hardware.camera";
+ private static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+ private static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+
+ public void testBleScan() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+ final int atom = Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+ final int key = BleScanStateChanged.STATE_FIELD_NUMBER;
+ final int stateOn = BleScanStateChanged.State.ON_VALUE;
+ final int stateOff = BleScanStateChanged.State.OFF_VALUE;
+ final int minTimeDiffMs = 1_500;
+ final int maxTimeDiffMs = 3_000;
+
+ List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, key,
+ stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, true);
+
+ BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
+ BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
+ assertTrue(a0.getState().getNumber() == stateOn);
+ assertTrue(a1.getState().getNumber() == stateOff);
+ }
+
+ public void testBleUnoptimizedScan() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+
+ final int atom = Atom.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED_FIELD_NUMBER;
+ final int key = BleUnoptimizedScanStateChanged.STATE_FIELD_NUMBER;
+ final int stateOn = BleUnoptimizedScanStateChanged.State.ON_VALUE;
+ final int stateOff = BleUnoptimizedScanStateChanged.State.OFF_VALUE;
+ final int minTimeDiffMs = 1_500;
+ final int maxTimeDiffMs = 3_000;
+
+ List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, key,
+ stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, true);
+
+ BleUnoptimizedScanStateChanged a0 = data.get(0).getAtom().getBleUnoptimizedScanStateChanged();
+ BleUnoptimizedScanStateChanged a1 = data.get(1).getAtom().getBleUnoptimizedScanStateChanged();
+ assertTrue(a0.getState().getNumber() == stateOn);
+ assertTrue(a1.getState().getNumber() == stateOff);
+
+ // Now repeat the test for optimized scanning and make sure no atoms are reported.
+ StatsdConfig.Builder conf = createConfigBuilder();
+ addAtomEvent(conf, atom, createKvm(key).setEqInt(stateOn));
+ addAtomEvent(conf, atom, createKvm(key).setEqInt(stateOff));
+ data = doDeviceMethod("testBleScanOptimized", conf);
+ assertTrue(data.isEmpty());
+ }
+
+ public void testBleScanResult() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_BLUETOOTH_LE, true)) return;
+ turnScreenOn(); // BLE results are not given unless screen is on. TODO: make more robust.
+
+ final int atom = Atom.BLE_SCAN_RESULT_RECEIVED_FIELD_NUMBER;
+ final int key = BleScanResultReceived.NUM_OF_RESULTS_FIELD_NUMBER;
+
+ StatsdConfig.Builder conf = createConfigBuilder();
+ addAtomEvent(conf, atom, createKvm(key).setGteInt(0));
+ List<EventMetricData> data = doDeviceMethod("testBleScanResult", conf);
+
+ assertTrue(data.size() >= 1);
+ BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
+ assertTrue(a0.getNumOfResults() >= 1);
+
+ turnScreenOff();
+ }
+
+ public void testCameraState() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_CAMERA, true) && !hasFeature(FEATURE_CAMERA_FRONT, true)) return;
+
+ final int atomTag = Atom.CAMERA_STATE_CHANGED_FIELD_NUMBER;
+ Set<Integer> cameraOn = new HashSet<>(Arrays.asList(CameraStateChanged.State.ON_VALUE));
+ Set<Integer> cameraOff = new HashSet<>(Arrays.asList(CameraStateChanged.State.OFF_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(cameraOn, cameraOff);
+
+ createAndUploadConfig(atomTag);
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testCameraState");
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+ atom -> atom.getCameraStateChanged().getState().getNumber());
+ }
+
+ public void testFlashlightState() throws Exception {
+ if (!TESTS_ENABLED)
+ return;
+ if (!hasFeature(FEATURE_CAMERA_FLASH, true))
+ return;
+
+ final int atomTag = Atom.FLASHLIGHT_STATE_CHANGED_FIELD_NUMBER;
+ final String name = "testFlashlight";
+
+ Set<Integer> flashlightOn = new HashSet<>(
+ Arrays.asList(FlashlightStateChanged.State.ON_VALUE));
+ Set<Integer> flashlightOff = new HashSet<>(
+ Arrays.asList(FlashlightStateChanged.State.OFF_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(flashlightOn, flashlightOff);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getFlashlightStateChanged().getState().getNumber());
+ }
+
+ public void testGpsScan() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_LOCATION_GPS, true)) return;
+ // Whitelist this app against background location request throttling
+ getDevice().executeShellCommand(String.format(
+ "settings put global location_background_throttle_package_whitelist %s",
+ DEVICE_SIDE_TEST_PACKAGE));
+
+ final int atom = Atom.GPS_SCAN_STATE_CHANGED_FIELD_NUMBER;
+ final int key = GpsScanStateChanged.STATE_FIELD_NUMBER;
+ final int stateOn = GpsScanStateChanged.State.ON_VALUE;
+ final int stateOff = GpsScanStateChanged.State.OFF_VALUE;
+ final int minTimeDiffMs = 500;
+ final int maxTimeDiffMs = 60_000;
+
+ List<EventMetricData> data = doDeviceMethodOnOff("testGpsScan", atom, key,
+ stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, true);
+
+ GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
+ GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
+ assertTrue(a0.getState().getNumber() == stateOn);
+ assertTrue(a1.getState().getNumber() == stateOff);
+ }
+
+ public void testWakeupAlarm() throws Exception {
+ if (!TESTS_ENABLED) return;
+
+ final int atomTag = Atom.WAKEUP_ALARM_OCCURRED_FIELD_NUMBER;
+
+ StatsdConfig.Builder config = createConfigBuilder();
+ addAtomEvent(config, atomTag);
+
+ List<EventMetricData> data = doDeviceMethod("testWakeupAlarm", config);
+ assertTrue(data.size() >= 1);
+ for (int i = 0; i < data.size(); i++) {
+ String tag = data.get(i).getAtom().getWakeupAlarmOccurred().getTag();
+ assertTrue(tag.equals("*walarm*:android.cts.statsd.testWakeupAlarm"));
+ }
+ }
+
+ public void testWifiLock() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_WIFI, true)) return;
+
+ final int atomTag = Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER;
+ Set<Integer> lockOn = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.ON_VALUE));
+ Set<Integer> lockOff = new HashSet<>(Arrays.asList(WifiLockStateChanged.State.OFF_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+ createAndUploadConfig(atomTag);
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWifiLock");
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+ atom -> atom.getWifiLockStateChanged().getState().getNumber());
+ }
+
+ public void testWifiScan() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_WIFI, true)) return;
+
+ final int atom = Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER;
+ final int key = WifiScanStateChanged.STATE_FIELD_NUMBER;
+ final int stateOn = WifiScanStateChanged.State.ON_VALUE;
+ final int stateOff = WifiScanStateChanged.State.OFF_VALUE;
+ final int minTimeDiffMs = 500;
+ final int maxTimeDiffMs = 60_000;
+ final boolean demandExactlyTwo = false; // Two scans are performed, so up to 4 atoms logged.
+
+ List<EventMetricData> data = doDeviceMethodOnOff("testWifiScan", atom, key,
+ stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, demandExactlyTwo);
+
+ assertTrue(data.size() >= 2);
+ assertTrue(data.size() <= 4);
+ WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
+ WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
+ assertTrue(a0.getState().getNumber() == stateOn);
+ assertTrue(a1.getState().getNumber() == stateOff);
+ }
+
+ public void testAudioState() throws Exception {
+ if (!TESTS_ENABLED) return;
+ if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
+
+ final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
+ final String name = "testAudioState";
+
+ Set<Integer> onState = new HashSet<>(
+ Arrays.asList(AudioStateChanged.State.ON_VALUE));
+ Set<Integer> offState = new HashSet<>(
+ Arrays.asList(AudioStateChanged.State.OFF_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+ Thread.sleep(WAIT_TIME_SHORT);
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Sorted list of events in order in which they occurred.
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, 200,
+ atom -> atom.getAudioStateChanged().getState().getNumber());
+ }
+
+ public void testMediaCodecActivity() throws Exception {
+ if (!TESTS_ENABLED) return;
+ final int atomTag = Atom.MEDIA_CODEC_ACTIVITY_CHANGED_FIELD_NUMBER;
+
+ Set<Integer> onState = new HashSet<>(
+ Arrays.asList(MediaCodecActivityChanged.State.ON_VALUE));
+ Set<Integer> offState = new HashSet<>(
+ Arrays.asList(MediaCodecActivityChanged.State.OFF_VALUE));
+
+ // Add state sets to the list in order.
+ List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+ createAndUploadConfig(atomTag);
+ Thread.sleep(WAIT_TIME_SHORT);
+ turnScreenOn();
+
+ getDevice().executeShellCommand(
+ "am start -n com.android.server.cts.device.statsd/.VideoPlayerActivity");
+
+ Thread.sleep(WAIT_TIME_LONG);
+ getDevice().executeShellCommand(
+ "am force-stop com.android.server.cts.device.statsd");
+
+ Thread.sleep(WAIT_TIME_SHORT);
+
+ // Sorted list of events in order in which they occurred.
+ List<EventMetricData> data = getEventMetricDataList();
+
+ // Assert that the events happened in the expected order.
+ assertStatesOccurred(stateSet, data, WAIT_TIME_LONG,
+ atom -> atom.getMediaCodecActivityChanged().getState().getNumber());
+ }
+}
diff --git a/hostsidetests/sustainedperf/AndroidTest.xml b/hostsidetests/sustainedperf/AndroidTest.xml
index e7cbee8..dd49674 100644
--- a/hostsidetests/sustainedperf/AndroidTest.xml
+++ b/hostsidetests/sustainedperf/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Sustained Performance host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/systemui/AndroidTest.xml b/hostsidetests/systemui/AndroidTest.xml
index 18e44ee..e1ae6e9 100644
--- a/hostsidetests/systemui/AndroidTest.xml
+++ b/hostsidetests/systemui/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS System UI host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="sysui" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/theme/AndroidTest.xml b/hostsidetests/theme/AndroidTest.xml
index fb5d6bb..beb9218 100644
--- a/hostsidetests/theme/AndroidTest.xml
+++ b/hostsidetests/theme/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Theme host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/theme/README b/hostsidetests/theme/README
index 4f93e5a..4bf32cd 100644
--- a/hostsidetests/theme/README
+++ b/hostsidetests/theme/README
@@ -67,6 +67,22 @@
./cts/hostsidetests/theme/generate_images.py
+There is an option to build locally an Android system image and use an emulator that is stored in
+Android source tree under "prebuilts/android-emulator/linux-x86_64/emulator". This option does not
+require a SDK and can be used to generate images with locally modified source code: for example
+right before making a test breaking change.
+
+ 1. From the console, set up your build environment for sdk_phone_x86_64 and build Android and CTS:
+
+ lunch sdk_phone_x86_64-userdebug && make -j32 && make cts -j32
+
+ 2. Use the reference image script to generate the reference images. The script
+ will automatically start the emulator in the required configurations and
+ install the resulting reference images in assets/<platform>/<dpi>.zip,
+ overwriting any existing images.
+
+ ./cts/hostsidetests/theme/generate_images.py local
+
A complete collection of reference images for a given API revision must include
a set for each possible DPI bucket (tvdpi, xxhdpi, etc.) that may be tested.
diff --git a/hostsidetests/theme/assets/P b/hostsidetests/theme/assets/P
deleted file mode 120000
index a5c750f..0000000
--- a/hostsidetests/theme/assets/P
+++ /dev/null
@@ -1 +0,0 @@
-27
\ No newline at end of file
diff --git a/hostsidetests/theme/assets/P/260dpi.zip b/hostsidetests/theme/assets/P/260dpi.zip
new file mode 100644
index 0000000..b34fde7
--- /dev/null
+++ b/hostsidetests/theme/assets/P/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/280dpi.zip b/hostsidetests/theme/assets/P/280dpi.zip
new file mode 100644
index 0000000..f4185a4
--- /dev/null
+++ b/hostsidetests/theme/assets/P/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/300dpi.zip b/hostsidetests/theme/assets/P/300dpi.zip
new file mode 100644
index 0000000..617aac3
--- /dev/null
+++ b/hostsidetests/theme/assets/P/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/340dpi.zip b/hostsidetests/theme/assets/P/340dpi.zip
new file mode 100644
index 0000000..98042c3
--- /dev/null
+++ b/hostsidetests/theme/assets/P/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/360dpi.zip b/hostsidetests/theme/assets/P/360dpi.zip
new file mode 100644
index 0000000..296dffb
--- /dev/null
+++ b/hostsidetests/theme/assets/P/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/400dpi.zip b/hostsidetests/theme/assets/P/400dpi.zip
new file mode 100644
index 0000000..b1f30ca
--- /dev/null
+++ b/hostsidetests/theme/assets/P/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/420dpi.zip b/hostsidetests/theme/assets/P/420dpi.zip
new file mode 100644
index 0000000..e159ab2
--- /dev/null
+++ b/hostsidetests/theme/assets/P/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/560dpi.zip b/hostsidetests/theme/assets/P/560dpi.zip
new file mode 100644
index 0000000..dff72b0
--- /dev/null
+++ b/hostsidetests/theme/assets/P/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/hdpi.zip b/hostsidetests/theme/assets/P/hdpi.zip
new file mode 100644
index 0000000..9fe11cc
--- /dev/null
+++ b/hostsidetests/theme/assets/P/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/ldpi.zip b/hostsidetests/theme/assets/P/ldpi.zip
new file mode 100644
index 0000000..8c5de21
--- /dev/null
+++ b/hostsidetests/theme/assets/P/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/mdpi.zip b/hostsidetests/theme/assets/P/mdpi.zip
new file mode 100644
index 0000000..ddd08b3
--- /dev/null
+++ b/hostsidetests/theme/assets/P/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/tvdpi.zip b/hostsidetests/theme/assets/P/tvdpi.zip
new file mode 100644
index 0000000..dc40713
--- /dev/null
+++ b/hostsidetests/theme/assets/P/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/xhdpi.zip b/hostsidetests/theme/assets/P/xhdpi.zip
new file mode 100644
index 0000000..4c63343
--- /dev/null
+++ b/hostsidetests/theme/assets/P/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/xxhdpi.zip b/hostsidetests/theme/assets/P/xxhdpi.zip
new file mode 100644
index 0000000..b4c6a78
--- /dev/null
+++ b/hostsidetests/theme/assets/P/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/P/xxxhdpi.zip b/hostsidetests/theme/assets/P/xxxhdpi.zip
new file mode 100644
index 0000000..891a0cd
--- /dev/null
+++ b/hostsidetests/theme/assets/P/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/avd.py b/hostsidetests/theme/avd.py
index 4c444a2..2a43129 100644
--- a/hostsidetests/theme/avd.py
+++ b/hostsidetests/theme/avd.py
@@ -46,8 +46,12 @@
port_adb = find_free_port()
port_tty = find_free_port()
# -no-window might be useful here
- emu_cmd = "%s -avd %s %s-ports %d,%d" \
- % (self._emu_path, self._name, self._opts, port_adb, port_tty)
+ if self._name == "local":
+ emu_cmd = "emulator %s-ports %d,%d -gpu on -wipe-data" \
+ % (self._opts, port_adb, port_tty)
+ else:
+ emu_cmd = "%s -avd %s %s-ports %d,%d" \
+ % (self._emu_path, self._name, self._opts, port_adb, port_tty)
print(emu_cmd)
emu_proc = subprocess.Popen(emu_cmd.split(" "), bufsize=-1, stdout=subprocess.PIPE,
diff --git a/hostsidetests/theme/generate_images.py b/hostsidetests/theme/generate_images.py
index 8b70f00..5dcc76f 100755
--- a/hostsidetests/theme/generate_images.py
+++ b/hostsidetests/theme/generate_images.py
@@ -50,7 +50,7 @@
class ParallelExecutor(threading.Thread):
- def __init__(self, tasks, q):
+ def __init__(self, tasks, setup, q):
threading.Thread.__init__(self)
self._q = q
self._tasks = tasks
@@ -60,7 +60,7 @@
def run(self):
try:
while True:
- config = q.get(block=True, timeout=2)
+ config = self._q.get(block=True, timeout=2)
for t in self._tasks:
try:
if t(self._setup, config):
@@ -70,7 +70,7 @@
except:
print("Failed to execute thread:", sys.exc_info()[0])
traceback.print_exc()
- q.task_done()
+ self._q.task_done()
except KeyboardInterrupt:
raise
except Empty:
@@ -86,7 +86,7 @@
result = 0
threads = []
for i in range(num_threads):
- t = ParallelExecutor(tasks, q)
+ t = ParallelExecutor(tasks, setup, q)
t.start()
threads.append(t)
for t in threads:
@@ -181,7 +181,10 @@
def start_emulator(name, density):
- emu_path = get_emulator_path()
+ if name == "local":
+ emu_path = ""
+ else:
+ emu_path = get_emulator_path()
# Start emulator for 560dpi, normal screen size.
test_avd = AVD(name, emu_path)
@@ -226,18 +229,21 @@
tasks = [do_capture]
setup = (theme_apk, out_path)
- devices = enumerate_android_devices('emulator')
+ devices = enumerate_android_devices()
- device_queue = Queue()
- for device in devices:
- device_queue.put(device)
+ if len(devices) > 0:
+ device_queue = Queue()
+ for device in devices:
+ device_queue.put(device)
- result = execute_parallel(tasks, setup, device_queue, len(devices))
+ result = execute_parallel(tasks, setup, device_queue, len(devices))
- if result > 0:
- print('Generated reference images for %(count)d devices' % {"count": result})
+ if result > 0:
+ print('Generated reference images for %(count)d devices' % {"count": result})
+ else:
+ print('Failed to generate reference images')
else:
- print('Failed to generate reference images')
+ print('No devices found')
if __name__ == '__main__':
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index 5c82e2a..017febc 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -93,7 +93,7 @@
mDevice = getDevice();
mHardwareType = mDevice.executeShellCommand(HARDWARE_TYPE_CMD).trim();
- final String density = getDensityBucketForDevice(mDevice, mHardwareType);
+ final String density = getDensityBucketForDevice(mDevice);
final String referenceZipAssetPath = String.format("/%s.zip", density);
mReferences = extractReferenceImages(referenceZipAssetPath);
@@ -231,11 +231,7 @@
return receiver.getOutput().contains("OK ");
}
- private static String getDensityBucketForDevice(ITestDevice device, String hardwareType) {
- if (hardwareType.contains("android.hardware.type.television")) {
- // references images for tv are under bucket "tvdpi".
- return "tvdpi";
- }
+ private static String getDensityBucketForDevice(ITestDevice device) {
final int density;
try {
density = getDensityForDevice(device);
@@ -250,6 +246,9 @@
case 160:
bucket = "mdpi";
break;
+ case 213:
+ bucket = "tvdpi";
+ break;
case 240:
bucket = "hdpi";
break;
diff --git a/hostsidetests/trustedvoice/AndroidTest.xml b/hostsidetests/trustedvoice/AndroidTest.xml
index 3e23236..2767992 100644
--- a/hostsidetests/trustedvoice/AndroidTest.xml
+++ b/hostsidetests/trustedvoice/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Trustedvoice host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/tv/AndroidTest.xml b/hostsidetests/tv/AndroidTest.xml
index d9edad0..07c38de 100644
--- a/hostsidetests/tv/AndroidTest.xml
+++ b/hostsidetests/tv/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS tv host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="tv" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsHostsideTvTests.jar" />
diff --git a/hostsidetests/tzdata/AndroidTest.xml b/hostsidetests/tzdata/AndroidTest.xml
index 1a2716e..39fc109 100644
--- a/hostsidetests/tzdata/AndroidTest.xml
+++ b/hostsidetests/tzdata/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS tzdatacheck host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsHostTzDataTests.jar" />
diff --git a/hostsidetests/ui/AndroidTest.xml b/hostsidetests/ui/AndroidTest.xml
index 9aaf7a4..88c515a 100644
--- a/hostsidetests/ui/AndroidTest.xml
+++ b/hostsidetests/ui/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS UI host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsUiHostTestCases.jar" />
diff --git a/hostsidetests/ui/appA/Android.mk b/hostsidetests/ui/appA/Android.mk
index 7da5606..3abc7a0 100644
--- a/hostsidetests/ui/appA/Android.mk
+++ b/hostsidetests/ui/appA/Android.mk
@@ -20,7 +20,10 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+ ctstestrunner \
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/ui/appA/AndroidManifest.xml b/hostsidetests/ui/appA/AndroidManifest.xml
index f336abd..dd2a901 100644
--- a/hostsidetests/ui/appA/AndroidManifest.xml
+++ b/hostsidetests/ui/appA/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity
android:name=".AppAActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
@@ -33,4 +35,4 @@
</activity>
</application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/ui/appB/Android.mk b/hostsidetests/ui/appB/Android.mk
index 07e2858..4501cf3 100644
--- a/hostsidetests/ui/appB/Android.mk
+++ b/hostsidetests/ui/appB/Android.mk
@@ -20,7 +20,10 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+ ctstestrunner \
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/ui/appB/AndroidManifest.xml b/hostsidetests/ui/appB/AndroidManifest.xml
index aaf7a2c..9d99377 100644
--- a/hostsidetests/ui/appB/AndroidManifest.xml
+++ b/hostsidetests/ui/appB/AndroidManifest.xml
@@ -20,6 +20,8 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity
android:name=".AppBActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
diff --git a/hostsidetests/ui/control/Android.mk b/hostsidetests/ui/control/Android.mk
index 688ace7..3b4f8da 100644
--- a/hostsidetests/ui/control/Android.mk
+++ b/hostsidetests/ui/control/Android.mk
@@ -22,6 +22,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsDeviceTaskSwitchingControl
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index 6dd8e6f..7078705 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -14,13 +14,15 @@
limitations under the License.
-->
<configuration description="Config for CTS App Usage host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAppUsageTestApp.apk" />
+ <option name="test-file-name" value="CtsAppUsageTestAppToo.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsAppUsageHostTestCases.jar" />
- <option name="runtime-hint" value="8m" />
+ <option name="runtime-hint" value="2m" />
</test>
</configuration>
diff --git a/hostsidetests/usage/app/Android.mk b/hostsidetests/usage/app/Android.mk
index 59626d6..3d51230 100644
--- a/hostsidetests/usage/app/Android.mk
+++ b/hostsidetests/usage/app/Android.mk
@@ -29,3 +29,23 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# Build a second one similar to the first
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsAppUsageTestAppToo
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.app.usage.apptoo
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/usage/app/AndroidManifest.xml b/hostsidetests/usage/app/AndroidManifest.xml
index 7d5ce48..303c26f 100755
--- a/hostsidetests/usage/app/AndroidManifest.xml
+++ b/hostsidetests/usage/app/AndroidManifest.xml
@@ -19,7 +19,7 @@
package="android.app.usage.app">
<application>
- <activity android:name=".TestActivity">
+ <activity android:name="android.app.usage.app.TestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
index 3cd7bda..1243cdd 100644
--- a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
+++ b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
@@ -16,18 +16,31 @@
package android.app.usage.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceTestCase;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public class AppIdleHostTest extends DeviceTestCase {
private static final String SETTINGS_APP_IDLE_CONSTANTS = "app_idle_constants";
private static final String TEST_APP_PACKAGE = "android.app.usage.app";
private static final String TEST_APP_CLASS = "TestActivity";
+ private static final String TEST_APP_PACKAGE2 = "android.app.usage.apptoo";
private static final long ACTIVITY_LAUNCH_WAIT_MILLIS = 500;
+ private static final int SB_ACTIVE = 10;
+ private static final int SB_WORKING_SET = 20;
+ private static final int SB_FREQUENT = 30;
+ private static final int SB_RARE = 40;
+ private static final int SB_NEVER = 50;
+
/**
* A reference to the device under test.
*/
@@ -104,6 +117,55 @@
}
}
+ private void setAppStandbyBucket(String packageName, int bucket) throws Exception {
+ mDevice.executeShellCommand(
+ String.format("am set-standby-bucket %s %s", packageName, bucket));
+ }
+
+ private int getAppStandbyBucket(String packageName) throws Exception {
+ String bucketString = mDevice.executeShellCommand(
+ String.format("am get-standby-bucket %s", packageName));
+ try {
+ return Integer.parseInt(bucketString.trim());
+ } catch (NumberFormatException nfe) {
+ }
+ return -1;
+ }
+
+ public void testSetAppStandbyBucket() throws Exception {
+ // Set to ACTIVE
+ setAppStandbyBucket(TEST_APP_PACKAGE, SB_ACTIVE);
+ assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+ // set to WORKING_SET
+ setAppStandbyBucket(TEST_APP_PACKAGE, 20);
+ assertEquals(20, getAppStandbyBucket(TEST_APP_PACKAGE));
+ }
+
+ public void testSetAppStandbyBuckets() throws Exception {
+ // Set multiple packages states
+ String command = String.format("am set-standby-bucket %s %d %s %d",
+ TEST_APP_PACKAGE, SB_FREQUENT, TEST_APP_PACKAGE2, SB_WORKING_SET);
+ mDevice.executeShellCommand(command);
+ assertEquals(SB_FREQUENT, getAppStandbyBucket(TEST_APP_PACKAGE));
+ assertEquals(SB_WORKING_SET, getAppStandbyBucket(TEST_APP_PACKAGE2));
+ }
+
+ public void testCantSetOwnStandbyBucket() throws Exception {
+ setAppStandbyBucket("com.android.shell", 40);
+ assertNotEquals(40, getAppStandbyBucket("com.android.shell"));
+ }
+
+ public void testOutOfBoundsStandbyBucket() throws Exception {
+ setAppStandbyBucket(TEST_APP_PACKAGE, SB_ACTIVE);
+ assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+ // Try lower than min
+ setAppStandbyBucket(TEST_APP_PACKAGE, SB_ACTIVE - 1);
+ assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+ // Try higher than max
+ setAppStandbyBucket(TEST_APP_PACKAGE, 50 + 1);
+ assertEquals(SB_ACTIVE, getAppStandbyBucket(TEST_APP_PACKAGE));
+ }
+
private static void sleepUninterrupted(long timeMillis) {
boolean interrupted;
do {
diff --git a/hostsidetests/usb/AndroidTest.xml b/hostsidetests/usb/AndroidTest.xml
index cfdcaea..bad4ff9 100644
--- a/hostsidetests/usb/AndroidTest.xml
+++ b/hostsidetests/usb/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS USB host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsUsbTests.jar" />
diff --git a/hostsidetests/usb/SerialTestApp/Android.mk b/hostsidetests/usb/SerialTestApp/Android.mk
index 2ddf30f..bbd55f6 100644
--- a/hostsidetests/usb/SerialTestApp/Android.mk
+++ b/hostsidetests/usb/SerialTestApp/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/hostsidetests/usb/SerialTestApp/AndroidManifest.xml b/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
index a75dd75..7c627ee 100644
--- a/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
+++ b/hostsidetests/usb/SerialTestApp/AndroidManifest.xml
@@ -17,6 +17,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.usb.serialtest">
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java b/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java
index a2b0c72..f47c2d2 100644
--- a/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java
+++ b/hostsidetests/usb/SerialTestApp/src/com/android/cts/usb/serialtest/UsbSerialTest.java
@@ -28,6 +28,6 @@
private static final String TAG = "CtsUsbSerialTest";
public void testSerial() throws Exception {
- Log.e(TAG, Build.SERIAL);
+ Log.e(TAG, Build.getSerial());
}
}
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 81cc265..47efe85 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -65,7 +65,7 @@
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
File app = buildHelper.getTestFile(APK_NAME);
String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
- mDevice.installPackage(app, false, options);
+ mDevice.installPackage(app, false, true, options);
}
@Override
diff --git a/hostsidetests/webkit/AndroidTest.xml b/hostsidetests/webkit/AndroidTest.xml
index 37d0467..414fad4 100644
--- a/hostsidetests/webkit/AndroidTest.xml
+++ b/hostsidetests/webkit/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS WebView host test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="webview" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/webkit/app/Android.mk b/hostsidetests/webkit/app/Android.mk
index 60e9f3f..fd2ee9a 100644
--- a/hostsidetests/webkit/app/Android.mk
+++ b/hostsidetests/webkit/app/Android.mk
@@ -27,6 +27,8 @@
ctstestserver \
ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
# When built, explicitly put it in the data partition.
#LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
diff --git a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
index 53f4a49..5953c5d 100644
--- a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
+++ b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
@@ -17,12 +17,13 @@
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestResult;
import com.android.ddmlib.testrunner.TestRunResult;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.testtype.DeviceTestCase;
-import java.util.Map;
+import java.util.Collection;
public class WebViewHostSideStartupTest extends DeviceTestCase {
private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
@@ -35,26 +36,35 @@
}
public void testCookieManager() throws DeviceNotAvailableException {
- assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
- "testCookieManagerBlockingUiThread"));
+ assertDeviceTestPasses("testCookieManagerBlockingUiThread");
}
public void testWebViewVersionApiOnUiThread() throws DeviceNotAvailableException {
- assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
- "testGetCurrentWebViewPackageOnUiThread"));
+ assertDeviceTestPasses("testGetCurrentWebViewPackageOnUiThread");
}
public void testWebViewVersionApi() throws DeviceNotAvailableException {
- assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
- "testGetCurrentWebViewPackage"));
+ assertDeviceTestPasses("testGetCurrentWebViewPackage");
}
public void testStrictMode() throws DeviceNotAvailableException {
- assertTrue(runDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG, DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
- "testStrictModeNotViolatedOnStartup"));
+ assertDeviceTestPasses("testStrictModeNotViolatedOnStartup");
}
- private boolean runDeviceTest(String packageName, String testClassName,
+ private void assertDeviceTestPasses(String testMethodName) throws DeviceNotAvailableException {
+ TestRunResult testRunResult = runSingleDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG,
+ DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
+ testMethodName);
+ assertTrue(testRunResult.isRunComplete());
+
+ Collection<TestResult> testResults = testRunResult.getTestResults().values();
+ // We're only running a single test.
+ assertEquals(1, testResults.size());
+ TestResult testResult = testResults.toArray(new TestResult[1])[0];
+ assertTrue(testResult.getStackTrace(), TestStatus.PASSED.equals(testResult.getStatus()));
+ }
+
+ private TestRunResult runSingleDeviceTest(String packageName, String testClassName,
String testMethodName) throws DeviceNotAvailableException {
testClassName = packageName + "." + testClassName;
@@ -65,7 +75,6 @@
CollectingTestListener listener = new CollectingTestListener();
assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
- TestRunResult runResult = listener.getCurrentRunResults();
- return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
+ return listener.getCurrentRunResults();
}
}
diff --git a/libs/deviceutillegacy/Android.mk b/libs/deviceutillegacy/Android.mk
index f169ca8..aa8255e 100644
--- a/libs/deviceutillegacy/Android.mk
+++ b/libs/deviceutillegacy/Android.mk
@@ -18,8 +18,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- junit \
- legacy-android-test
+ junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := \
$(call all-java-files-under, src)
diff --git a/tests/AlarmManager/Android.mk b/tests/AlarmManager/Android.mk
new file mode 100755
index 0000000..a1676ab
--- /dev/null
+++ b/tests/AlarmManager/Android.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, app/src)
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsAlarmManagerTestCases
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/AlarmManager/AndroidManifest.xml b/tests/AlarmManager/AndroidManifest.xml
new file mode 100644
index 0000000..f557b51
--- /dev/null
+++ b/tests/AlarmManager/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.alarmmanager.cts" >
+
+ <application android:label="Cts Alarm Manager Test">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:functionalTest="true"
+ android:targetPackage="android.alarmmanager.cts"
+ android:label="Alarm Manager Tests"/>
+</manifest>
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
new file mode 100644
index 0000000..a8c8955
--- /dev/null
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="Config for CTS Alarm Manager test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAlarmManagerTestCases.apk" />
+ <option name="test-file-name" value="AlarmTestApp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.alarmmanager.cts" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+
+</configuration>
diff --git a/tests/AlarmManager/app/Android.mk b/tests/AlarmManager/app/Android.mk
new file mode 100644
index 0000000..b2023e9
--- /dev/null
+++ b/tests/AlarmManager/app/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AlarmTestApp
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
new file mode 100644
index 0000000..ceb8fb9
--- /dev/null
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.alarmmanager.alarmtestapp.cts">
+
+ <application>
+ <activity android:name=".TestAlarmActivity"
+ android:exported="true" />
+ <receiver android:name=".TestAlarmReceiver" />
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmActivity.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmActivity.java
new file mode 100644
index 0000000..8f666dd8
--- /dev/null
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmActivity.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmmanager.alarmtestapp.cts;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * This Activity is to be used as part of
+ * {@link android.alarmmanager.cts.BackgroundRestrictedAlarmsTest}
+ */
+public class TestAlarmActivity extends Activity {
+ private static final String TAG = TestAlarmActivity.class.getSimpleName();
+ private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
+
+ public static final String ACTION_SET_ALARM = PACKAGE_NAME + ".action.SET_ALARM";
+ public static final String EXTRA_TRIGGER_TIME = PACKAGE_NAME + ".extra.TRIGGER_TIME";
+ public static final String EXTRA_REPEAT_INTERVAL = PACKAGE_NAME + ".extra.REPEAT_INTERVAL";
+ public static final String EXTRA_TYPE = PACKAGE_NAME + ".extra.TYPE";
+ public static final String ACTION_SET_ALARM_CLOCK = PACKAGE_NAME + ".action.SET_ALARM_CLOCK";
+ public static final String EXTRA_ALARM_CLOCK_INFO = PACKAGE_NAME + ".extra.ALARM_CLOCK_INFO";
+ public static final String ACTION_CANCEL_ALL_ALARMS = PACKAGE_NAME + ".action.CANCEL_ALARMS";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final AlarmManager am = getSystemService(AlarmManager.class);
+ final Intent intent = getIntent();
+ final Intent receiverIntent = new Intent(this, TestAlarmReceiver.class);
+ receiverIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final PendingIntent alarmClockSender =
+ PendingIntent.getBroadcast(this, 0, receiverIntent, 0);
+ final PendingIntent alarmSender = PendingIntent.getBroadcast(this, 1, receiverIntent, 0);
+ switch (intent.getAction()) {
+ case ACTION_SET_ALARM_CLOCK:
+ if (!intent.hasExtra(EXTRA_ALARM_CLOCK_INFO)) {
+ Log.e(TAG, "No alarm clock supplied");
+ break;
+ }
+ final AlarmManager.AlarmClockInfo alarmClockInfo =
+ intent.getParcelableExtra(EXTRA_ALARM_CLOCK_INFO);
+ Log.d(TAG, "Setting alarm clock " + alarmClockInfo);
+ am.setAlarmClock(alarmClockInfo, alarmClockSender);
+ break;
+ case ACTION_SET_ALARM:
+ if (!intent.hasExtra(EXTRA_TYPE) || !intent.hasExtra(EXTRA_TRIGGER_TIME)) {
+ Log.e(TAG, "Alarm type or trigger time not supplied");
+ break;
+ }
+ final int type = intent.getIntExtra(EXTRA_TYPE, 0);
+ final long triggerTime = intent.getLongExtra(EXTRA_TRIGGER_TIME, 0);
+ final long interval = intent.getLongExtra(EXTRA_REPEAT_INTERVAL, 0);
+ Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+ + ", interval=" + interval);
+ if (interval > 0) {
+ am.setRepeating(type, triggerTime, interval, alarmSender);
+ } else {
+ am.setExact(type, triggerTime, alarmSender);
+ }
+ break;
+ case ACTION_CANCEL_ALL_ALARMS:
+ Log.d(TAG, "Cancelling all alarms");
+ am.cancel(alarmClockSender);
+ am.cancel(alarmSender);
+ break;
+ default:
+ Log.e(TAG, "Unspecified action " + intent.getAction());
+ break;
+ }
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
new file mode 100644
index 0000000..a083e08
--- /dev/null
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmmanager.alarmtestapp.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestAlarmReceiver extends BroadcastReceiver{
+ private static final String TAG = TestAlarmReceiver.class.getSimpleName();
+ private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
+ public static final String ACTION_REPORT_ALARM_EXPIRED = PACKAGE_NAME + ".action.ALARM_EXPIRED";
+ public static final String EXTRA_ALARM_COUNT = PACKAGE_NAME + ".extra.ALARM_COUNT";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int count = intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 1);
+ Log.d(TAG, "Alarm expired " + count + " times");
+ final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM_EXPIRED);
+ reportAlarmIntent.putExtra(EXTRA_ALARM_COUNT, count);
+ reportAlarmIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ context.sendBroadcast(reportAlarmIntent);
+ }
+}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
new file mode 100644
index 0000000..2b37bfd
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmmanager.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.alarmmanager.alarmtestapp.cts.TestAlarmActivity;
+import android.alarmmanager.alarmtestapp.cts.TestAlarmReceiver;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BackgroundRestrictedAlarmsTest {
+ private static final String TAG = BackgroundRestrictedAlarmsTest.class.getSimpleName();
+ private static final String TEST_APP_PACKAGE = "android.alarmmanager.alarmtestapp.cts";
+ private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestAlarmActivity";
+ private static final String APP_OP_RUN_ANY_IN_BACKGROUND = "RUN_ANY_IN_BACKGROUND";
+ private static final String APP_OP_MODE_ALLOWED = "allow";
+ private static final String APP_OP_MODE_IGNORED = "ignore";
+
+ private static final long DEFAULT_WAIT = 1_000;
+ private static final long POLL_INTERVAL = 1_000;
+ private static final long MIN_REPEATING_INTERVAL = 10_000;
+
+ private Object mLock = new Object();
+ private Context mContext;
+ private ComponentName mAlarmActivity;
+ private UiDevice mUiDevice;
+ private int mAlarmCount;
+
+ private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Received action " + intent.getAction()
+ + " elapsed: " + SystemClock.elapsedRealtime());
+ synchronized (mLock) {
+ mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
+ }
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mAlarmActivity = new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY);
+ mAlarmCount = 0;
+ updateAlarmManagerConstants();
+ setAppOpsMode(APP_OP_MODE_IGNORED);
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
+ mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
+ }
+
+ private void scheduleAlarm(int type, long triggerMillis, long interval) {
+ final Intent setAlarmIntent = new Intent(TestAlarmActivity.ACTION_SET_ALARM);
+ setAlarmIntent.setComponent(mAlarmActivity);
+ setAlarmIntent.putExtra(TestAlarmActivity.EXTRA_TYPE, type);
+ setAlarmIntent.putExtra(TestAlarmActivity.EXTRA_TRIGGER_TIME, triggerMillis);
+ setAlarmIntent.putExtra(TestAlarmActivity.EXTRA_REPEAT_INTERVAL, interval);
+ setAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(setAlarmIntent);
+ }
+
+ private void scheduleAlarmClock(long triggerRTC) {
+ AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerRTC, null);
+
+ final Intent setAlarmClockIntent = new Intent(TestAlarmActivity.ACTION_SET_ALARM_CLOCK);
+ setAlarmClockIntent.setComponent(mAlarmActivity);
+ setAlarmClockIntent.putExtra(TestAlarmActivity.EXTRA_ALARM_CLOCK_INFO, alarmInfo);
+ setAlarmClockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(setAlarmClockIntent);
+ }
+
+ private static int getMinExpectedExpirations(long now, long start, long interval) {
+ if (now - start <= 1000) {
+ return 0;
+ }
+ return 1 + (int)((now - start - 1000)/interval);
+ }
+
+ @Test
+ public void testRepeatingAlarmBlocked() throws Exception {
+ final long interval = MIN_REPEATING_INTERVAL;
+ final long triggerElapsed = SystemClock.elapsedRealtime() + interval;
+ scheduleAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerElapsed, interval);
+ Thread.sleep(DEFAULT_WAIT);
+ makeTestPackageIdle();
+ Thread.sleep(2 * interval);
+ assertFalse("Alarm got triggered even under restrictions", waitForAlarms(1, DEFAULT_WAIT));
+ Thread.sleep(interval);
+ setAppOpsMode(APP_OP_MODE_ALLOWED);
+ // The alarm is due to go off about 3 times by now. Adding some tolerance just in case
+ // an expiration is due right about now.
+ final int minCount = getMinExpectedExpirations(SystemClock.elapsedRealtime(),
+ triggerElapsed, interval);
+ assertTrue("Alarm should have expired at least " + minCount
+ + " times when restrictions were lifted", waitForAlarms(minCount, DEFAULT_WAIT));
+ }
+
+ @Test
+ public void testAlarmClockNotBlocked() throws Exception {
+ final long nowRTC = System.currentTimeMillis();
+ final long waitInterval = 10_000;
+ final long triggerRTC = nowRTC + waitInterval;
+ scheduleAlarmClock(triggerRTC);
+ Thread.sleep(waitInterval);
+ assertTrue("AlarmClock did not go off as scheduled when under restrictions",
+ waitForAlarms(1, DEFAULT_WAIT));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ deleteAlarmManagerConstants();
+ setAppOpsMode(APP_OP_MODE_ALLOWED);
+ // Cancel any leftover alarms
+ final Intent cancelAlarmsIntent = new Intent(TestAlarmActivity.ACTION_CANCEL_ALL_ALARMS);
+ cancelAlarmsIntent.setComponent(mAlarmActivity);
+ cancelAlarmsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(cancelAlarmsIntent);
+ mContext.unregisterReceiver(mAlarmStateReceiver);
+ // Broadcast unregister may race with the next register in setUp
+ Thread.sleep(DEFAULT_WAIT);
+ }
+
+ private void updateAlarmManagerConstants() throws IOException {
+ String cmd = "settings put global alarm_manager_constants min_interval="
+ + MIN_REPEATING_INTERVAL;
+ mUiDevice.executeShellCommand(cmd);
+ }
+
+ private void deleteAlarmManagerConstants() throws IOException {
+ mUiDevice.executeShellCommand("settings delete global alarm_manager_constants");
+ }
+
+ private void setAppOpsMode(String mode) throws IOException {
+ StringBuilder commandBuilder = new StringBuilder("appops set ")
+ .append(TEST_APP_PACKAGE)
+ .append(" ")
+ .append(APP_OP_RUN_ANY_IN_BACKGROUND)
+ .append(" ")
+ .append(mode);
+ mUiDevice.executeShellCommand(commandBuilder.toString());
+ }
+
+ private void makeTestPackageIdle() throws IOException {
+ StringBuilder commandBuilder = new StringBuilder("am make-uid-idle --user current ")
+ .append(TEST_APP_PACKAGE);
+ mUiDevice.executeShellCommand(commandBuilder.toString());
+ }
+
+ private boolean waitForAlarms(int expectedAlarms, long timeout) throws InterruptedException {
+ final long deadLine = SystemClock.uptimeMillis() + timeout;
+ int alarmCount;
+ do {
+ Thread.sleep(POLL_INTERVAL);
+ synchronized (mLock) {
+ alarmCount = mAlarmCount;
+ }
+ } while (alarmCount < expectedAlarms && SystemClock.uptimeMillis() < deadLine);
+ return alarmCount >= expectedAlarms;
+ }
+}
diff --git a/tests/JobScheduler/Android.mk b/tests/JobScheduler/Android.mk
index e58a494..fa6a5d1 100755
--- a/tests/JobScheduler/Android.mk
+++ b/tests/JobScheduler/Android.mk
@@ -22,9 +22,12 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ub-uiautomator android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, JobTestApp/src)
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
@@ -32,7 +35,7 @@
# Must match the package name in CtsTestCaseList.mk
LOCAL_PACKAGE_NAME := CtsJobSchedulerTestCases
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/JobScheduler/AndroidManifest.xml b/tests/JobScheduler/AndroidManifest.xml
index e433252..4c9625d 100755
--- a/tests/JobScheduler/AndroidManifest.xml
+++ b/tests/JobScheduler/AndroidManifest.xml
@@ -43,8 +43,5 @@
android:name="android.support.test.runner.AndroidJUnitRunner"
android:label="JobScheduler device-side tests"
android:targetPackage="android.jobscheduler.cts" >
- <meta-data
- android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
</manifest>
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index 855ad48..d7a5988 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -14,12 +14,14 @@
limitations under the License.
-->
<configuration description="Config for CTS Job Scheduler test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsJobSchedulerTestCases.apk" />
<option name="test-file-name" value="CtsJobSchedulerJobPerm.apk" />
<option name="test-file-name" value="CtsJobSchedulerSharedUid.apk" />
+ <option name="test-file-name" value="CtsJobTestApp.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.jobscheduler.cts" />
diff --git a/tests/JobScheduler/JobTestApp/Android.mk b/tests/JobScheduler/JobTestApp/Android.mk
new file mode 100644
index 0000000..f7b7a1d
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsJobTestApp
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/JobScheduler/JobTestApp/AndroidManifest.xml b/tests/JobScheduler/JobTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..866c5d4
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.jobscheduler.cts.jobtestapp">
+
+ <!-- This application schedules jobs independently of the test instrumentation to make
+ it possible to test behaviour for different app states, whitelists and device idle modes -->
+ <application>
+ <service android:name=".TestJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" />
+ <activity android:name=".TestActivity"
+ android:exported="true" />
+ <receiver android:name=".TestJobSchedulerReceiver"
+ android:exported="true" />
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestActivity.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestActivity.java
new file mode 100644
index 0000000..0368f05
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.jobscheduler.cts.jobtestapp;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * Just a dummy activity to keep the test app process in the foreground state when desired.
+ */
+public class TestActivity extends Activity {
+ private static final String TAG = TestActivity.class.getSimpleName();
+ private static final String PACKAGE_NAME = "android.jobscheduler.cts.jobtestapp";
+ private static final long DEFAULT_WAIT_DURATION = 30_000;
+
+ static final int FINISH_ACTIVITY_MSG = 1;
+ public static final String ACTION_FINISH_ACTIVITY = PACKAGE_NAME + ".action.FINISH_ACTIVITY";
+
+ Handler mFinishHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case FINISH_ACTIVITY_MSG:
+ Log.d(TAG, "Finishing test activity: " + TestActivity.class.getCanonicalName());
+ unregisterReceiver(mFinishReceiver);
+ finish();
+ }
+ }
+ };
+
+ final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mFinishHandler.removeMessages(FINISH_ACTIVITY_MSG);
+ mFinishHandler.sendEmptyMessage(FINISH_ACTIVITY_MSG);
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstance) {
+ Log.d(TAG, "Started test activity: " + TestActivity.class.getCanonicalName());
+ super.onCreate(savedInstance);
+ // automatically finish after 30 seconds.
+ mFinishHandler.sendEmptyMessageDelayed(FINISH_ACTIVITY_MSG, DEFAULT_WAIT_DURATION);
+ registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
+ }
+}
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
new file mode 100644
index 0000000..97e8d74
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.jobscheduler.cts.jobtestapp;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Schedules jobs for this package but does not, by itself, occupy a foreground uid state
+ * while doing so.
+ */
+public class TestJobSchedulerReceiver extends BroadcastReceiver {
+ private static final String TAG = TestJobSchedulerReceiver.class.getSimpleName();
+ private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
+
+ public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
+ public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
+ public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
+ public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
+ public static final int JOB_INITIAL_BACKOFF = 10_000;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final ComponentName jobServiceComponent = new ComponentName(context, TestJobService.class);
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ switch (intent.getAction()) {
+ case ACTION_CANCEL_JOBS:
+ jobScheduler.cancelAll();
+ Log.d(TAG, "Cancelled all jobs for " + context.getPackageName());
+ break;
+ case ACTION_SCHEDULE_JOB:
+ final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
+ final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
+ JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
+ .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(0)
+ .setImportantWhileForeground(allowInIdle);
+ final int result = jobScheduler.schedule(jobBuilder.build());
+ if (result != JobScheduler.RESULT_SUCCESS) {
+ Log.e(TAG, "Could not schedule job " + jobId);
+ } else {
+ Log.d(TAG, "Successfully scheduled job with id " + jobId);
+ }
+ break;
+ default:
+ Log.e(TAG, "Unknown action " + intent.getAction());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobService.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobService.java
new file mode 100644
index 0000000..55031c3
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.jobscheduler.cts.jobtestapp;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestJobService extends JobService {
+ private static final String TAG = TestJobService.class.getSimpleName();
+ private static final String PACKAGE_NAME = "android.jobscheduler.cts.jobtestapp";
+ public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
+ public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
+ public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Log.i(TAG, "Test job executing: " + params.getJobId());
+ final Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
+ reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+ sendBroadcast(reportJobStartIntent);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ Log.i(TAG, "Test job stopped executing: " + params.getJobId());
+ final Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
+ reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+ sendBroadcast(reportJobStopIntent);
+ return true;
+ }
+}
diff --git a/tests/JobScheduler/jobperm/AndroidManifest.xml b/tests/JobScheduler/jobperm/AndroidManifest.xml
index 14eb02b..493c3e8 100755
--- a/tests/JobScheduler/jobperm/AndroidManifest.xml
+++ b/tests/JobScheduler/jobperm/AndroidManifest.xml
@@ -27,6 +27,8 @@
<uses-permission android:name="android.jobscheduler.cts.jobperm.perm" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<!-- Need a way for another app to try to access the permission. So create a content
provider which is enforced by the permission -->
<provider android:name=".JobPermProvider"
diff --git a/tests/JobScheduler/shareduid/AndroidManifest.xml b/tests/JobScheduler/shareduid/AndroidManifest.xml
index 756a067..7b4bb56 100755
--- a/tests/JobScheduler/shareduid/AndroidManifest.xml
+++ b/tests/JobScheduler/shareduid/AndroidManifest.xml
@@ -19,5 +19,6 @@
package="android.jobscheduler.cts.shareduid"
android:sharedUserId="android.jobscheduler.cts.shared.uid">
<application>
+ <uses-library android:name="android.test.runner" />
</application>
</manifest>
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 94aee20..964853c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -52,6 +52,8 @@
ArrayList<JobWorkItem> mReceivedWork = new ArrayList<>();
+ ArrayList<JobWorkItem> mPendingCompletions = new ArrayList<>();
+
private boolean mWaitingForStop;
@Override
@@ -104,6 +106,8 @@
Log.i(TAG, "Received work #" + index + ": " + work.getIntent());
mReceivedWork.add(work);
+ int flags = 0;
+
if (index < expectedWork.length) {
TestWorkItem expected = expectedWork[index];
int grantFlags = work.getIntent().getFlags();
@@ -170,15 +174,33 @@
}
}
- if ((expected.flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
+ flags = expected.flags;
+
+ if ((flags & TestWorkItem.FLAG_WAIT_FOR_STOP) != 0) {
Log.i(TAG, "Now waiting to stop");
mWaitingForStop = true;
TestEnvironment.getTestEnvironment().notifyWaitingForStop();
return true;
}
+
+ if ((flags & TestWorkItem.FLAG_COMPLETE_NEXT) != 0) {
+ if (!processNextPendingCompletion()) {
+ TestEnvironment.getTestEnvironment().notifyExecution(params,
+ 0, 0, null,
+ "Expected to complete next pending work but there was none: "
+ + " @ #" + index);
+ return false;
+ }
+ }
}
- mParams.completeWork(work);
+ if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) != 0) {
+ mPendingCompletions.add(work);
+ } else if ((flags & TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) != 0) {
+ mPendingCompletions.add(0, work);
+ } else {
+ mParams.completeWork(work);
+ }
if (index < expectedWork.length) {
TestWorkItem expected = expectedWork[index];
@@ -195,6 +217,20 @@
index++;
}
+
+ if (processNextPendingCompletion()) {
+ // We had some pending completions, clean them all out...
+ while (processNextPendingCompletion()) {
+ }
+ // ...and we need to do a final dequeue to complete the job, which should not
+ // return any remaining work.
+ if ((work = params.dequeueWork()) != null) {
+ TestEnvironment.getTestEnvironment().notifyExecution(params,
+ 0, 0, null,
+ "Expected no remaining work after dequeue pending, but got: " + work);
+ }
+ }
+
Log.i(TAG, "Done with all work at #" + index);
// We don't notifyExecution here because we want to make sure the job properly
// stops itself.
@@ -219,6 +255,16 @@
}
}
+ boolean processNextPendingCompletion() {
+ if (mPendingCompletions.size() <= 0) {
+ return false;
+ }
+
+ JobWorkItem next = mPendingCompletions.remove(0);
+ mParams.completeWork(next);
+ return true;
+ }
+
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "Received stop callback");
@@ -227,7 +273,24 @@
}
public static final class TestWorkItem {
+ /**
+ * Stop processing work for now, waiting for the service to be stopped.
+ */
public static final int FLAG_WAIT_FOR_STOP = 1<<0;
+ /**
+ * Don't complete this work now, instead push it on the back of the stack of
+ * pending completions.
+ */
+ public static final int FLAG_DELAY_COMPLETE_PUSH_BACK = 1<<1;
+ /**
+ * Don't complete this work now, instead insert to the top of the stack of
+ * pending completions.
+ */
+ public static final int FLAG_DELAY_COMPLETE_PUSH_TOP = 1<<2;
+ /**
+ * Complete next pending completion on the stack before completing this one.
+ */
+ public static final int FLAG_COMPLETE_NEXT = 1<<3;
public final Intent intent;
public final JobInfo jobInfo;
@@ -247,6 +310,16 @@
requireUrisNotGranted = null;
}
+ public TestWorkItem(Intent _intent, int _flags) {
+ intent = _intent;
+ jobInfo = null;
+ flags = _flags;
+ deliveryCount = 1;
+ subitems = null;
+ requireUrisGranted = null;
+ requireUrisNotGranted = null;
+ }
+
public TestWorkItem(Intent _intent, int _flags, int _deliveryCount) {
intent = _intent;
jobInfo = null;
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index 65cd02b..e28675d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -28,6 +28,7 @@
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
import android.os.SystemClock;
import android.util.Log;
@@ -106,6 +107,10 @@
boolean curNotLow = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
"cmd jobscheduler get-battery-not-low").trim());
assertEquals(notLow, curNotLow);
+ IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryState = getContext().registerReceiver(null, filter);
+ assertEquals(notLow,
+ !batteryState.getBooleanExtra(BatteryManager.EXTRA_BATTERY_LOW, notLow));
}
String getJobState() throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
new file mode 100644
index 0000000..0918a3b
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.jobscheduler.cts;
+
+import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STARTED;
+import static android.jobscheduler.cts.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
+import static android.jobscheduler.cts.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
+import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
+import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.job.JobParameters;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.jobscheduler.cts.jobtestapp.TestActivity;
+import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that temp whitelisted apps can run jobs if all the other constraints are met
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class DeviceIdleJobsTest {
+ private static final String TAG = DeviceIdleJobsTest.class.getSimpleName();
+ private static final String TEST_APP_PACKAGE = "android.jobscheduler.cts.jobtestapp";
+ private static final String TEST_APP_RECEIVER = TEST_APP_PACKAGE + ".TestJobSchedulerReceiver";
+ private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestActivity";
+ private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000;
+ private static final long POLL_INTERVAL = 500;
+ private static final long DEFAULT_WAIT_TIMEOUT = 1000;
+
+ private Context mContext;
+ private UiDevice mUiDevice;
+ private PowerManager mPowerManager;
+ private long mTempWhitelistExpiryElapsed;
+ private int mTestJobId;
+ private int mTestPackageUid;
+ private boolean mDeviceInDoze;
+ /* accesses must be synchronized on itself */
+ private final TestJobStatus mTestJobStatus = new TestJobStatus();
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Received action " + intent.getAction());
+ switch (intent.getAction()) {
+ case ACTION_JOB_STARTED:
+ case ACTION_JOB_STOPPED:
+ final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
+ Log.d(TAG, "JobId: " + params.getJobId());
+ synchronized (mTestJobStatus) {
+ mTestJobStatus.running = ACTION_JOB_STARTED.equals(intent.getAction());
+ mTestJobStatus.jobId = params.getJobId();
+ }
+ break;
+ case ACTION_DEVICE_IDLE_MODE_CHANGED:
+ case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
+ synchronized (DeviceIdleJobsTest.this) {
+ mDeviceInDoze = mPowerManager.isDeviceIdleMode();
+ Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
+ }
+ break;
+ }
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ mDeviceInDoze = mPowerManager.isDeviceIdleMode();
+ mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
+ mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
+ mTestJobStatus.reset();
+ mTempWhitelistExpiryElapsed = -1;
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_JOB_STARTED);
+ intentFilter.addAction(ACTION_JOB_STOPPED);
+ intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
+ intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
+ mContext.registerReceiver(mReceiver, intentFilter);
+ assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
+ makeTestPackageIdle();
+ makeTestPackageStandbyActive();
+ }
+
+
+ @Test
+ public void testAllowWhileIdleJobInForeground() throws Exception {
+ toggleDeviceIdleState(true);
+ sendScheduleJobBroadcast(true);
+ assertFalse("Job started while in background", awaitJobStart(5_000));
+ startAndKeepTestActivity();
+ assertTrue("Job with allow_while_idle flag did not start when the app was in fg",
+ awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
+ toggleDeviceIdleState(true);
+ Thread.sleep(DEFAULT_WAIT_TIMEOUT);
+ sendScheduleJobBroadcast(true);
+ assertFalse("Job started without being tempwhitelisted", awaitJobStart(5_000));
+ tempWhitelistTestApp(5_000);
+ assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted",
+ awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testForegroundJobsStartImmediately() throws Exception {
+ sendScheduleJobBroadcast(false);
+ assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ toggleDeviceIdleState(true);
+ assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+ startAndKeepTestActivity();
+ toggleDeviceIdleState(false);
+ assertTrue("Job for foreground app did not start immediately when device exited doze",
+ awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testBackgroundJobsDelayed() throws Exception {
+ sendScheduleJobBroadcast(false);
+ assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ toggleDeviceIdleState(true);
+ assertTrue("Job did not stop on entering doze", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+ toggleDeviceIdleState(false);
+ assertFalse("Job for background app started immediately when device exited doze",
+ awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
+ assertTrue("Job for background app did not start after the expected delay of "
+ + BACKGROUND_JOBS_EXPECTED_DELAY + "ms", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ toggleDeviceIdleState(false);
+ final Intent cancelJobsIntent = new Intent(TestJobSchedulerReceiver.ACTION_CANCEL_JOBS);
+ cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
+ cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.sendBroadcast(cancelJobsIntent);
+ mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
+ mContext.unregisterReceiver(mReceiver);
+ Thread.sleep(500); // To avoid any race between unregister and the next register in setUp
+ waitUntilTestAppNotInTempWhitelist();
+ }
+
+ private boolean isTestAppTempWhitelisted() throws Exception {
+ final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
+ for (String line : output.split("\n")) {
+ if (line.contains("UID="+mTestPackageUid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void startAndKeepTestActivity() {
+ final Intent testActivity = new Intent();
+ testActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ testActivity.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
+ mContext.startActivity(testActivity);
+ }
+
+ private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
+ final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
+ scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mTestJobId);
+ scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle);
+ scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
+ mContext.sendBroadcast(scheduleJobIntent);
+ }
+
+ private void toggleDeviceIdleState(final boolean idle) throws Exception {
+ mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
+ assertTrue("Could not change device idle state to " + idle,
+ waitUntilTrue(DEFAULT_WAIT_TIMEOUT, () -> {
+ synchronized (DeviceIdleJobsTest.this) {
+ return mDeviceInDoze == idle;
+ }
+ }));
+ }
+
+ private void tempWhitelistTestApp(long duration) throws Exception {
+ mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
+ + " " + TEST_APP_PACKAGE);
+ mTempWhitelistExpiryElapsed = SystemClock.elapsedRealtime() + duration;
+ }
+
+ private void makeTestPackageIdle() throws Exception {
+ mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
+ }
+
+ private void makeTestPackageStandbyActive() throws Exception {
+ mUiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE + " active");
+ }
+
+ private boolean waitUntilTestAppNotInTempWhitelist() throws Exception {
+ long now;
+ boolean interrupted = false;
+ while ((now = SystemClock.elapsedRealtime()) < mTempWhitelistExpiryElapsed) {
+ try {
+ Thread.sleep(mTempWhitelistExpiryElapsed - now);
+ } catch (InterruptedException iexc) {
+ interrupted = true;
+ }
+ }
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ return waitUntilTrue(DEFAULT_WAIT_TIMEOUT, () -> !isTestAppTempWhitelisted());
+ }
+
+ private boolean awaitJobStart(long maxWait) throws Exception {
+ return waitUntilTrue(maxWait, () -> {
+ synchronized (mTestJobStatus) {
+ return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
+ }
+ });
+ }
+
+ private boolean awaitJobStop(long maxWait) throws Exception {
+ return waitUntilTrue(maxWait, () -> {
+ synchronized (mTestJobStatus) {
+ return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running;
+ }
+ });
+ }
+
+ private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
+ final long deadLine = SystemClock.uptimeMillis() + maxWait;
+ do {
+ Thread.sleep(POLL_INTERVAL);
+ } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
+ return condition.isTrue();
+ }
+
+ private static final class TestJobStatus {
+ int jobId;
+ boolean running;
+ private void reset() {
+ running = false;
+ }
+ }
+
+ private interface Condition {
+ boolean isTrue() throws Exception;
+ }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
index 6d2599f..604ccce 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/EnqueueJobWorkTest.java
@@ -186,6 +186,87 @@
}
/**
+ * Test basic enqueueing batches of work that will be executed in parallel.
+ */
+ public void testEnqueueParallel2Work() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not fire.",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
+ * Test basic enqueueing batches of work that will be executed in parallel and completed
+ * in reverse order.
+ */
+ public void testEnqueueParallel2ReverseWork() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not fire.",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
+ * Test basic enqueueing batches of work that will be executed in parallel.
+ */
+ public void testEnqueueMultipleParallelWork() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ Intent work3 = new Intent("work3");
+ Intent work4 = new Intent("work4");
+ Intent work5 = new Intent("work5");
+ Intent work6 = new Intent("work6");
+ Intent work7 = new Intent("work7");
+ Intent work8 = new Intent("work8");
+ TestWorkItem[] work = new TestWorkItem[] {
+ new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+ new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK
+ | TestWorkItem.FLAG_COMPLETE_NEXT),
+ new TestWorkItem(work4, TestWorkItem.FLAG_COMPLETE_NEXT),
+ new TestWorkItem(work5, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+ new TestWorkItem(work6, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP),
+ new TestWorkItem(work7, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP
+ | TestWorkItem.FLAG_COMPLETE_NEXT),
+ new TestWorkItem(work8) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(work);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work3));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work4));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work5));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work6));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work7));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work8));
+ kTestEnvironment.readyToWork();
+ assertTrue("Job with work enqueued did not fire.",
+ kTestEnvironment.awaitExecution());
+ compareWork(work, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
* Test job getting stopped while processing work and that work being redelivered.
*/
public void testEnqueueMultipleRedeliver() throws Exception {
@@ -238,6 +319,61 @@
}
/**
+ * Test job getting stopped while processing work in parallel and that work being redelivered.
+ */
+ public void testEnqueueMultipleParallelRedeliver() throws Exception {
+ Intent work1 = new Intent("work1");
+ Intent work2 = new Intent("work2");
+ Intent work3 = new Intent("work3");
+ Intent work4 = new Intent("work4");
+ Intent work5 = new Intent("work5");
+ Intent work6 = new Intent("work6");
+ TestWorkItem[] initialWork = new TestWorkItem[] {
+ new TestWorkItem(work1),
+ new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK),
+ new TestWorkItem(work4, TestWorkItem.FLAG_WAIT_FOR_STOP, 1) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWaitForStop();
+ kTestEnvironment.setExpectedWork(initialWork);
+ JobInfo ji = mBuilder.setOverrideDeadline(0).build();
+ mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work3));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work4));
+ kTestEnvironment.readyToWork();
+
+ // Now wait for the job to get to the point where it is processing the last
+ // work and waiting for it to be stopped.
+ assertTrue("Job with work enqueued did not wait to stop.",
+ kTestEnvironment.awaitWaitingForStop());
+
+ // Cause the job to timeout (stop) immediately, and wait for its execution to finish.
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler timeout "
+ + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID);
+ assertTrue("Job with work enqueued did not finish.",
+ kTestEnvironment.awaitExecution());
+ compareWork(initialWork, kTestEnvironment.getLastReceivedWork());
+
+ // Now we are going to add some more work, restart the job, and see if it correctly
+ // redelivers the last work and delivers the new work.
+ TestWorkItem[] finalWork = new TestWorkItem[] {
+ new TestWorkItem(work2, 0, 2), new TestWorkItem(work3, 0, 2),
+ new TestWorkItem(work4, 0, 2), new TestWorkItem(work5), new TestWorkItem(work6) };
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setExpectedWork(finalWork);
+ mJobScheduler.enqueue(ji, new JobWorkItem(work5));
+ mJobScheduler.enqueue(ji, new JobWorkItem(work6));
+ kTestEnvironment.readyToWork();
+ SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run "
+ + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID);
+
+ assertTrue("Restarted with work enqueued did not execute.",
+ kTestEnvironment.awaitExecution());
+ compareWork(finalWork, kTestEnvironment.getLastReceivedWork());
+ }
+
+ /**
* Test basic enqueueing batches of work.
*/
public void testEnqueueMultipleUriGrantWork() throws Exception {
diff --git a/tests/ProcessTest/Android.mk b/tests/ProcessTest/Android.mk
index 2feff2e..6b15240 100644
--- a/tests/ProcessTest/Android.mk
+++ b/tests/ProcessTest/Android.mk
@@ -24,7 +24,9 @@
LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_PACKAGE_NAME := ProcessTests
diff --git a/tests/acceleration/Android.mk b/tests/acceleration/Android.mk
index 81869bf..cef4379 100644
--- a/tests/acceleration/Android.mk
+++ b/tests/acceleration/Android.mk
@@ -25,7 +25,9 @@
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
- ctstestrunner compatibility-device-util legacy-android-test
+ ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/acceleration/AndroidTest.xml b/tests/acceleration/AndroidTest.xml
index 01ff4cc..b28f845 100644
--- a/tests/acceleration/AndroidTest.xml
+++ b/tests/acceleration/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Acceleration test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="location" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/accessibility/Android.mk b/tests/accessibility/Android.mk
index 4cd7e92..cf41720 100644
--- a/tests/accessibility/Android.mk
+++ b/tests/accessibility/Android.mk
@@ -26,6 +26,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index aa9db4f..5348373 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -32,7 +32,7 @@
public class AccessibilityEventTest extends TestCase {
/** The number of properties of the {@link AccessibilityEvent} class. */
- private static final int NON_STATIC_FIELD_COUNT = 29;
+ private static final int NON_STATIC_FIELD_COUNT = 32;
/**
* Test that no new fields have been added without updating the
@@ -206,6 +206,8 @@
sentEvent.setMaxScrollY(1);
sentEvent.setScrollX(1);
sentEvent.setScrollY(1);
+ sentEvent.setScrollDeltaX(3);
+ sentEvent.setScrollDeltaY(3);
sentEvent.setToIndex(1);
sentEvent.setScrollable(true);
sentEvent.setAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
@@ -261,6 +263,10 @@
receivedEvent.getScrollX());
assertSame("scrollY has incorect value", expectedEvent.getScrollY(),
receivedEvent.getScrollY());
+ assertSame("scrollDeltaX has incorect value", expectedEvent.getScrollDeltaX(),
+ receivedEvent.getScrollDeltaX());
+ assertSame("scrollDeltaY has incorect value", expectedEvent.getScrollDeltaY(),
+ receivedEvent.getScrollDeltaY());
assertSame("toIndex has incorect value", expectedEvent.getToIndex(),
receivedEvent.getToIndex());
assertSame("scrollable has incorect value", expectedEvent.isScrollable(),
@@ -269,6 +275,8 @@
receivedEvent.getMovementGranularity());
assertSame("action has incorect value", expectedEvent.getAction(),
receivedEvent.getAction());
+ assertSame("windowChangeTypes has incorect value", expectedEvent.getWindowChanges(),
+ receivedEvent.getWindowChanges());
assertSame("parcelableData has incorect value",
((Message) expectedEvent.getParcelableData()).what,
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 7862cb4..5bf02c8 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -35,7 +35,7 @@
public class AccessibilityRecordTest extends AndroidTestCase {
/** The number of properties of the {@link AccessibilityEvent} class. */
- private static final int NON_STATIC_FIELD_COUNT = 22;
+ private static final int NON_STATIC_FIELD_COUNT = 24;
/**
* Test that no new fields have been added without updating the
diff --git a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
index 9783cb8..01275f3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
@@ -181,6 +181,9 @@
final Object waitLockForA11yOff = new Object();
AccessibilityManager manager = (AccessibilityManager) instrumentation
.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (!manager.isEnabled()) {
+ return;
+ }
manager.addAccessibilityStateChangeListener(
new AccessibilityManager.AccessibilityStateChangeListener() {
@Override
diff --git a/tests/accessibilityservice/Android.mk b/tests/accessibilityservice/Android.mk
index 9a3bbe2..79e47ec 100644
--- a/tests/accessibilityservice/Android.mk
+++ b/tests/accessibilityservice/Android.mk
@@ -20,8 +20,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- mockito-target-minus-junit4 \
- legacy-android-test
+ mockito-target-minus-junit4
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -33,3 +34,5 @@
LOCAL_SDK_VERSION := test_current
include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index cf6b774..fe5e18a 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -28,27 +28,28 @@
<activity
android:label="@string/accessibility_end_to_end_test_activity"
- android:name=".AccessibilityEndToEndActivity" />
+ android:name=".activities.AccessibilityEndToEndActivity" />
<activity
android:label="@string/accessibility_query_window_test_activity"
- android:name=".AccessibilityWindowQueryActivity"
+ android:name=".activities.AccessibilityWindowQueryActivity"
android:supportsPictureInPicture="true" />
<activity
android:label="@string/accessibility_view_tree_reporting_test_activity"
- android:name=".AccessibilityViewTreeReportingActivity" />
+ android:name=".activities.AccessibilityViewTreeReportingActivity" />
<activity
android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
- android:name=".AccessibilityFocusAndInputFocusSyncActivity" />
+ android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity" />
<activity
android:label="@string/accessibility_text_traversal_test_activity"
- android:name=".AccessibilityTextTraversalActivity"/>
+ android:name=".activities.AccessibilityTextTraversalActivity"/>
<activity android:label="Activity for testing window accessibility reporting"
- android:name=".AccessibilityWindowReportingActivity"/>
+ android:name=".activities.AccessibilityWindowReportingActivity"
+ android:supportsPictureInPicture="true"/>
<activity
android:label="Full screen activity for gesture dispatch testing"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index 6edfe8d..0454a298 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -14,11 +14,16 @@
limitations under the License.
-->
<configuration description="Config for CTS accessibility service test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsAccessibilityServiceTestCases.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAccessibilityWidgetProvider.apk" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.accessibilityservice.cts" />
<option name="runtime-hint" value="2m12s" />
diff --git a/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml b/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml
index cb3d153..6a7ccf5 100644
--- a/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml
+++ b/tests/accessibilityservice/res/layout/accessibility_focus_and_input_focus_sync_test.xml
@@ -77,6 +77,7 @@
android:id="@+id/secondButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:screenReaderFocusable="true"
android:text="@string/secondButton" />
</LinearLayout>
diff --git a/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml
index 912c9c4..f4f6089 100644
--- a/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_gesture_dispatch_a11y_service.xml
@@ -16,9 +16,9 @@
-->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:description="@string/stub_gesture_dispatch_a11y_service_description"
- android:accessibilityEventTypes="typeAllMask"
- android:accessibilityFeedbackType="feedbackGeneric"
- android:accessibilityFlags="flagDefault"
- android:canPerformGestures="true"
- android:notificationTimeout="0" />
+ android:description="@string/stub_gesture_dispatch_a11y_service_description"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:accessibilityFlags="flagDefault"
+ android:canPerformGestures="true"
+ android:notificationTimeout="0" />
diff --git a/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml
index 110f741..2559392 100644
--- a/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_magnification_a11y_service.xml
@@ -15,10 +15,11 @@
-->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:accessibilityEventTypes="typeAllMask"
- android:accessibilityFeedbackType="feedbackGeneric"
- android:canRetrieveWindowContent="true"
- android:canRequestTouchExplorationMode="true"
- android:canRequestEnhancedWebAccessibility="true"
- android:canRequestFilterKeyEvents="true"
- android:canControlMagnification="true" />
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:canRetrieveWindowContent="true"
+ android:canRequestTouchExplorationMode="true"
+ android:canRequestEnhancedWebAccessibility="true"
+ android:canRequestFilterKeyEvents="true"
+ android:canControlMagnification="true"
+ android:canPerformGestures="true"/>
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndActivity.java
deleted file mode 100644
index 9a26ac7..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndActivity.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2010 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.accessibilityservice.cts;
-
-import android.accessibilityservice.cts.R;
-
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * This class is an {@link android.app.Activity} used to perform end-to-end
- * testing of the accessibility feature by interaction with the
- * UI widgets.
- */
-public class AccessibilityEndToEndActivity extends AccessibilityTestActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.end_to_end_test);
-
- ListAdapter listAdapter = new BaseAdapter() {
- public View getView(int position, View convertView, ViewGroup parent) {
- TextView textView = (TextView) View
- .inflate(AccessibilityEndToEndActivity.this, R.layout.list_view_row, null);
- textView.setText((String) getItem(position));
- return textView;
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public Object getItem(int position) {
- if (position == 0) {
- return AccessibilityEndToEndActivity.this.getString(R.string.first_list_item);
- } else {
- return AccessibilityEndToEndActivity.this.getString(R.string.second_list_item);
- }
- }
-
- public int getCount() {
- return 2;
- }
- };
-
- ListView listView = (ListView) findViewById(R.id.listview);
- listView.setAdapter(listAdapter);
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index b14b76a..ad1d712 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,6 +16,7 @@
package android.accessibilityservice.cts;
+import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
@@ -24,22 +25,28 @@
import android.app.PendingIntent;
import android.app.Service;
import android.app.UiAutomation;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.os.Process;
import android.test.suitebuilder.annotation.MediumTest;
import android.text.TextUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
-import android.accessibilityservice.cts.R;
-
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* This class performs end-to-end testing of the accessibility feature by
@@ -51,6 +58,14 @@
private static final String LOG_TAG = "AccessibilityEndToEndTest";
+ private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
+ "appwidget grantbind --package android.accessibilityservice.cts --user 0";
+
+ private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
+ "appwidget revokebind --package android.accessibilityservice.cts --user 0";
+
+ private static final String APP_WIDGET_PROVIDER_PACKAGE = "foo.bar.baz";
+
/**
* Creates a new instance for testing {@link AccessibilityEndToEndActivity}.
*/
@@ -430,6 +445,158 @@
}
}
+ @MediumTest
+ public void testPackageNameCannotBeFaked() throws Exception {
+ getActivity().runOnUiThread(() -> {
+ // Set the activity to report fake package for events and nodes
+ getActivity().setReportedPackageName("foo.bar.baz");
+
+ // Make sure node package cannot be faked
+ AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+ .getRootInActiveWindow();
+ assertPackageName(root, getActivity().getPackageName());
+ });
+
+ // Make sure event package cannot be faked
+ try {
+ getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
+ getInstrumentation().runOnMainSync(() ->
+ getActivity().findViewById(R.id.button).requestFocus())
+ , (AccessibilityEvent event) ->
+ event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
+ && event.getPackageName().equals(getActivity().getPackageName())
+ , TIMEOUT_ASYNC_PROCESSING);
+ } catch (TimeoutException e) {
+ fail("Events from fake package should be fixed to use the correct package");
+ }
+ }
+
+ @MediumTest
+ public void testPackageNameCannotBeFakedAppWidget() throws Exception {
+ if (!hasAppWidgets()) {
+ return;
+ }
+
+ getInstrumentation().runOnMainSync(() -> {
+ // Set the activity to report fake package for events and nodes
+ getActivity().setReportedPackageName(APP_WIDGET_PROVIDER_PACKAGE);
+
+ // Make sure we cannot report nodes as if from the widget package
+ AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+ .getRootInActiveWindow();
+ assertPackageName(root, getActivity().getPackageName());
+ });
+
+ // Make sure we cannot send events as if from the widget package
+ try {
+ getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
+ getInstrumentation().runOnMainSync(() ->
+ getActivity().findViewById(R.id.button).requestFocus())
+ , (AccessibilityEvent event) ->
+ event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
+ && event.getPackageName().equals(getActivity().getPackageName())
+ , TIMEOUT_ASYNC_PROCESSING);
+ } catch (TimeoutException e) {
+ fail("Should not be able to send events from a widget package if no widget hosted");
+ }
+
+ // Create a host and start listening.
+ final AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
+ host.deleteHost();
+ host.startListening();
+
+ // Well, app do not have this permission unless explicitly granted
+ // by the user. Now we will pretend for the user and grant it.
+ grantBindAppWidgetPermission();
+
+ // Allocate an app widget id to bind.
+ final int appWidgetId = host.allocateAppWidgetId();
+ try {
+ // Grab a provider we defined to be bound.
+ final AppWidgetProviderInfo provider = getAppWidgetProviderInfo();
+
+ // Bind the widget.
+ final boolean widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed(
+ appWidgetId, provider.getProfile(), provider.provider, null);
+ assertTrue(widgetBound);
+
+ // Make sure the app can use the package of a widget it hosts
+ getInstrumentation().runOnMainSync(() -> {
+ // Make sure we can report nodes as if from the widget package
+ AccessibilityNodeInfo root = getInstrumentation().getUiAutomation()
+ .getRootInActiveWindow();
+ assertPackageName(root, APP_WIDGET_PROVIDER_PACKAGE);
+ });
+
+ // Make sure we can send events as if from the widget package
+ try {
+ getInstrumentation().getUiAutomation().executeAndWaitForEvent(() ->
+ getInstrumentation().runOnMainSync(() ->
+ getActivity().findViewById(R.id.button).performClick())
+ , (AccessibilityEvent event) ->
+ event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED
+ && event.getPackageName().equals(APP_WIDGET_PROVIDER_PACKAGE)
+ , TIMEOUT_ASYNC_PROCESSING);
+ } catch (TimeoutException e) {
+ fail("Should be able to send events from a widget package if widget hosted");
+ }
+ } finally {
+ // Clean up.
+ host.deleteAppWidgetId(appWidgetId);
+ host.deleteHost();
+ revokeBindAppWidgetPermission();
+ }
+ }
+
+ private static void assertPackageName(AccessibilityNodeInfo node, String packageName) {
+ if (node == null) {
+ return;
+ }
+ assertEquals(packageName, node.getPackageName());
+ final int childCount = node.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ AccessibilityNodeInfo child = node.getChild(i);
+ if (child != null) {
+ assertPackageName(child, packageName);
+ }
+ }
+ }
+
+ private AppWidgetProviderInfo getAppWidgetProviderInfo() {
+ final ComponentName componentName = new ComponentName(
+ "foo.bar.baz", "foo.bar.baz.MyAppWidgetProvider");
+ final List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+ final int providerCount = providers.size();
+ for (int i = 0; i < providerCount; i++) {
+ final AppWidgetProviderInfo provider = providers.get(i);
+ if (componentName.equals(provider.provider)
+ && Process.myUserHandle().equals(provider.getProfile())) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private void grantBindAppWidgetPermission() throws Exception {
+ ShellCommandBuilder.execShellCommand(getInstrumentation().getUiAutomation(),
+ GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
+ }
+
+ private void revokeBindAppWidgetPermission() throws Exception {
+ ShellCommandBuilder.execShellCommand(getInstrumentation().getUiAutomation(),
+ REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
+ }
+
+ private AppWidgetManager getAppWidgetManager() {
+ return (AppWidgetManager) getInstrumentation().getTargetContext()
+ .getSystemService(Context.APPWIDGET_SERVICE);
+ }
+
+ private boolean hasAppWidgets() {
+ return getInstrumentation().getTargetContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
+ }
+
/**
* Compares all properties of the <code>first</code> and the
* <code>second</code>.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncActivity.java
deleted file mode 100644
index 62831a4..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Copyright (C) 2012 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.accessibilityservice.cts;
-
-import android.os.Bundle;
-
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity for testing the accessibility focus APIs exposed to
- * accessibility services. These APIs allow moving accessibility
- * focus in the view tree from an AccessiiblityService. Specifically,
- * this activity is for verifying the the sync between accessibility
- * and input focus.
- */
-public class AccessibilityFocusAndInputFocusSyncActivity extends AccessibilityTestActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.accessibility_focus_and_input_focus_sync_test);
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 08e231f..aad045f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -17,8 +17,11 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
+import android.app.Instrumentation;
import android.app.UiAutomation;
import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -26,6 +29,7 @@
import java.util.LinkedList;
import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Test cases for testing the accessibility focus APIs exposed to accessibility
@@ -228,4 +232,29 @@
}
}
}
+
+ public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
+ final Instrumentation instrumentation = getInstrumentation();
+ final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+ final AccessibilityNodeInfo secondButton = uiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByText(getString(R.string.secondButton)).get(0);
+ assertTrue("Screen reader focusability not propagated from xml to accessibility",
+ secondButton.isScreenReaderFocusable());
+
+ // Verify the setter and getter work
+ final AtomicBoolean isScreenReaderFocusableAtomic = new AtomicBoolean(false);
+ instrumentation.runOnMainSync(() -> {
+ View secondButtonView = getActivity().findViewById(R.id.secondButton);
+ secondButtonView.setScreenReaderFocusable(false);
+ isScreenReaderFocusableAtomic.set(secondButtonView.isScreenReaderFocusable());
+ });
+
+ assertFalse("isScreenReaderFocusable did not change after value set",
+ isScreenReaderFocusableAtomic.get());
+
+ secondButton.refresh();
+ assertFalse(
+ "Screen reader focusability not propagated to accessibility after calling setter",
+ secondButton.isScreenReaderFocusable());
+ }
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index df8c6f3..cc68b72 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -14,6 +14,17 @@
package android.accessibilityservice.cts;
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.AsyncUtils.awaitCancellation;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.diff;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
+import static android.accessibilityservice.cts.utils.GestureUtils.path;
+import static android.accessibilityservice.cts.utils.GestureUtils.times;
+
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.any;
import static org.hamcrest.CoreMatchers.both;
@@ -21,24 +32,26 @@
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.graphics.Path;
-import android.graphics.Point;
import android.graphics.PointF;
import android.os.Bundle;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
-import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.TextView;
+
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -72,7 +85,6 @@
final List<MotionEvent> mMotionEvents = new ArrayList<>();
StubGestureAccessibilityService mService;
MyTouchListener mMyTouchListener = new MyTouchListener();
- MyGestureCallback mCallback;
TextView mFullScreenTextView;
int[] mViewLocation = new int[2];
boolean mGotUpEvent;
@@ -107,7 +119,6 @@
mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
mMotionEvents.clear();
- mCallback = new MyGestureCallback();
mGotUpEvent = false;
}
@@ -126,10 +137,8 @@
return;
}
- Point clickPoint = new Point(10, 20);
- GestureDescription click = createClickInViewBounds(clickPoint);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(click, mCallback, null));
- mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
+ PointF clickPoint = new PointF(10, 20);
+ dispatch(clickWithinView(clickPoint), GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(any(MotionEvent.class), 2);
assertEquals(2, mMotionEvents.size());
@@ -161,10 +170,8 @@
return;
}
- Point clickPoint = new Point(10, 20);
- GestureDescription longClick = createLongClickInViewBounds(clickPoint);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(longClick, mCallback, null));
- mCallback.assertGestureCompletes(
+ PointF clickPoint = new PointF(10, 20);
+ dispatch(longClickWithinView(clickPoint),
ViewConfiguration.getLongPressTimeout() + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(any(MotionEvent.class), 2);
@@ -183,13 +190,12 @@
return;
}
- Point startPoint = new Point(10, 20);
- Point endPoint = new Point(20, 40);
+ PointF startPoint = new PointF(10, 20);
+ PointF endPoint = new PointF(20, 40);
int gestureTime = 500;
- GestureDescription swipe = createSwipeInViewBounds(startPoint, endPoint, gestureTime);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(swipe, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ dispatch(swipeWithinView(startPoint, endPoint, gestureTime),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(IS_ACTION_UP, 1);
int numEvents = mMotionEvents.size();
@@ -206,30 +212,31 @@
assertTrue(moveEvent.getEventTime() >= lastEventTime);
float fractionOfSwipe =
((float) (moveEvent.getEventTime() - downEvent.getEventTime())) / gestureTime;
- float fractionX = ((float) (endPoint.x - startPoint.x)) * fractionOfSwipe + 0.5f;
- float fractionY = ((float) (endPoint.y - startPoint.y)) * fractionOfSwipe + 0.5f;
- Point intermediatePoint = new Point(startPoint);
- intermediatePoint.offset((int) fractionX, (int) fractionY);
+ PointF intermediatePoint = add(startPoint,
+ ceil(times(fractionOfSwipe, diff(endPoint, startPoint))));
assertThat(moveEvent, both(IS_ACTION_MOVE).and(isAtPoint(intermediatePoint)));
lastEventTime = moveEvent.getEventTime();
}
}
+ public void dispatch(GestureDescription gesture, int timeoutMs) {
+ await(dispatchGesture(mService, gesture), timeoutMs, MILLISECONDS);
+ }
+
public void testSlowSwipe_shouldNotContainMovesForTinyMovement() throws InterruptedException {
if (!mHasTouchScreen) {
return;
}
- Point startPoint = new Point(10, 20);
- Point intermediatePoint1 = new Point(10, 21);
- Point intermediatePoint2 = new Point(11, 21);
- Point intermediatePoint3 = new Point(11, 22);
- Point endPoint = new Point(11, 22);
+ PointF startPoint = new PointF(10, 20);
+ PointF intermediatePoint1 = new PointF(10, 21);
+ PointF intermediatePoint2 = new PointF(11, 21);
+ PointF intermediatePoint3 = new PointF(11, 22);
+ PointF endPoint = new PointF(11, 22);
int gestureTime = 1000;
- GestureDescription swipe = createSwipeInViewBounds(startPoint, endPoint, gestureTime);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(swipe, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ dispatch(swipeWithinView(startPoint, endPoint, gestureTime),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(IS_ACTION_UP, 1);
assertEquals(5, mMotionEvents.size());
@@ -245,16 +252,14 @@
return;
}
- Point centerPoint = new Point(50, 60);
+ PointF centerPoint = new PointF(50, 60);
int startSpacing = 100;
int endSpacing = 50;
int gestureTime = 500;
float pinchTolerance = 2.0f;
- GestureDescription pinch = createPinchInViewBounds(centerPoint, startSpacing,
- endSpacing, 45.0F, gestureTime);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(pinch, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ dispatch(pinchWithinView(centerPoint, startSpacing, endSpacing, 45.0F, gestureTime),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(IS_ACTION_UP, 1);
int numEvents = mMotionEvents.size();
@@ -313,13 +318,10 @@
magRegionCenterPoint.set(magnificationController.getCenterX(),
magnificationController.getCenterY());
});
- final PointF magRegionOffsetPoint = new PointF();
- magRegionOffsetPoint.set(magRegionCenterPoint);
- magRegionOffsetPoint.offset(CLICK_OFFSET_X, CLICK_OFFSET_Y);
+ final PointF magRegionOffsetPoint
+ = add(magRegionCenterPoint, CLICK_OFFSET_X, CLICK_OFFSET_Y);
- final PointF magRegionOffsetClickPoint = new PointF();
- magRegionOffsetClickPoint.set(magRegionCenterPoint);
- magRegionOffsetClickPoint.offset(
+ final PointF magRegionOffsetClickPoint = add(magRegionCenterPoint,
CLICK_OFFSET_X * MAGNIFICATION_FACTOR, CLICK_OFFSET_Y * MAGNIFICATION_FACTOR);
try {
@@ -331,16 +333,16 @@
assertTrue("Failed to set scale", setScale.get());
// Click in the center of the magnification region
- GestureDescription magRegionCenterClick = createClick(magRegionCenterPoint);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(
- magRegionCenterClick, mCallback, null));
- mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
+ dispatch(new GestureDescription.Builder()
+ .addStroke(click(magRegionCenterPoint))
+ .build(),
+ GESTURE_COMPLETION_TIMEOUT);
// Click at a slightly offset point
- GestureDescription magRegionOffsetClick = createClick(magRegionOffsetClickPoint);
- mService.runOnServiceSync(() -> mService.doDispatchGesture(
- magRegionOffsetClick, mCallback, null));
- mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
+ dispatch(new GestureDescription.Builder()
+ .addStroke(click(magRegionOffsetClickPoint))
+ .build(),
+ GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(any(MotionEvent.class), 4);
} finally {
// Reset magnification
@@ -376,30 +378,25 @@
return;
}
- Point start = new Point(10, 20);
- Point mid1 = new Point(20, 20);
- Point mid2 = new Point(20, 25);
- Point end = new Point(20, 30);
+ PointF start = new PointF(10, 20);
+ PointF mid1 = new PointF(20, 20);
+ PointF mid2 = new PointF(20, 25);
+ PointF end = new PointF(20, 30);
int gestureTime = 500;
StrokeDescription s1 = new StrokeDescription(
- linePathInViewBounds(start, mid1), 0, gestureTime, true);
+ lineWithinView(start, mid1), 0, gestureTime, true);
StrokeDescription s2 = s1.continueStroke(
- linePathInViewBounds(mid1, mid2), 0, gestureTime, true);
+ lineWithinView(mid1, mid2), 0, gestureTime, true);
StrokeDescription s3 = s2.continueStroke(
- linePathInViewBounds(mid2, end), 0, gestureTime, false);
+ lineWithinView(mid2, end), 0, gestureTime, false);
+
GestureDescription gesture1 = new GestureDescription.Builder().addStroke(s1).build();
GestureDescription gesture2 = new GestureDescription.Builder().addStroke(s2).build();
GestureDescription gesture3 = new GestureDescription.Builder().addStroke(s3).build();
-
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture1, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
- mCallback.reset();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture2, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
- mCallback.reset();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture3, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ dispatch(gesture1, gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ dispatch(gesture2, gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ dispatch(gesture3, gestureTime + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(IS_ACTION_UP, 1);
assertThat(mMotionEvents.get(0), allOf(IS_ACTION_DOWN, isAtPoint(start)));
@@ -415,25 +412,24 @@
return;
}
- Point startPoint = new Point(10, 20);
- Point midPoint = new Point(20, 20);
- Point endPoint = new Point(20, 30);
+ PointF startPoint = new PointF(10, 20);
+ PointF midPoint = new PointF(20, 20);
+ PointF endPoint = new PointF(20, 30);
int gestureTime = 500;
- StrokeDescription stroke1 = new StrokeDescription(
- linePathInViewBounds(startPoint, midPoint), 0, gestureTime, true);
- GestureDescription gesture1 = new GestureDescription.Builder().addStroke(stroke1).build();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture1, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ StrokeDescription stroke1 =
+ new StrokeDescription(lineWithinView(startPoint, midPoint), 0, gestureTime, true);
+ dispatch(new GestureDescription.Builder().addStroke(stroke1).build(),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(both(IS_ACTION_MOVE).and(isAtPoint(midPoint)), 1);
- StrokeDescription stroke2 = stroke1.continueStroke(
- linePathInViewBounds(endPoint, midPoint), 0, gestureTime, false);
- GestureDescription gesture2 = new GestureDescription.Builder().addStroke(stroke2).build();
- mCallback.reset();
+ StrokeDescription stroke2 =
+ stroke1.continueStroke(lineWithinView(endPoint, midPoint), 0, gestureTime, false);
mMotionEvents.clear();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture2, mCallback, null));
- mCallback.assertGestureCancels(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ awaitCancellation(
+ dispatchGesture(mService,
+ new GestureDescription.Builder().addStroke(stroke2).build()),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT, MILLISECONDS);
waitForMotionEvents(IS_ACTION_CANCEL, 1);
assertEquals(1, mMotionEvents.size());
@@ -444,23 +440,20 @@
return;
}
- Point startPoint = new Point(10, 20);
- Point midPoint = new Point(20, 20);
- Point endPoint = new Point(20, 30);
+ PointF startPoint = new PointF(10, 20);
+ PointF midPoint = new PointF(20, 20);
+ PointF endPoint = new PointF(20, 30);
int gestureTime = 500;
- StrokeDescription stroke1 = new StrokeDescription(
- linePathInViewBounds(startPoint, midPoint), 0, gestureTime, true);
- GestureDescription gesture1 = new GestureDescription.Builder().addStroke(stroke1).build();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture1, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ StrokeDescription stroke1 =
+ new StrokeDescription(lineWithinView(startPoint, midPoint), 0, gestureTime, true);
+ dispatch(new GestureDescription.Builder().addStroke(stroke1).build(),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT);
- StrokeDescription stroke2 = new StrokeDescription(
- linePathInViewBounds(midPoint, endPoint), 0, gestureTime, false);
- GestureDescription gesture2 = new GestureDescription.Builder().addStroke(stroke2).build();
- mCallback.reset();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture2, mCallback, null));
- mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ StrokeDescription stroke2 =
+ new StrokeDescription(lineWithinView(midPoint, endPoint), 0, gestureTime, false);
+ dispatch(new GestureDescription.Builder().addStroke(stroke2).build(),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT);
waitForMotionEvents(IS_ACTION_UP, 1);
@@ -479,20 +472,20 @@
return;
}
- Point startPoint = new Point(10, 20);
- Point midPoint = new Point(20, 20);
- Point endPoint = new Point(20, 30);
+ PointF startPoint = new PointF(10, 20);
+ PointF midPoint = new PointF(20, 20);
+ PointF endPoint = new PointF(20, 30);
int gestureTime = 500;
- StrokeDescription stroke1 = new StrokeDescription(
- linePathInViewBounds(startPoint, midPoint), 0, gestureTime, true);
+ StrokeDescription stroke1 =
+ new StrokeDescription(lineWithinView(startPoint, midPoint), 0, gestureTime, true);
- StrokeDescription stroke2 = stroke1.continueStroke(
- linePathInViewBounds(midPoint, endPoint), 0, gestureTime, false);
- GestureDescription gesture = new GestureDescription.Builder().addStroke(stroke2).build();
- mCallback.reset();
- mService.runOnServiceSync(() -> mService.doDispatchGesture(gesture, mCallback, null));
- mCallback.assertGestureCancels(gestureTime + GESTURE_COMPLETION_TIMEOUT);
+ StrokeDescription stroke2 =
+ stroke1.continueStroke(lineWithinView(midPoint, endPoint), 0, gestureTime, false);
+ awaitCancellation(
+ dispatchGesture(mService,
+ new GestureDescription.Builder().addStroke(stroke2).build()),
+ gestureTime + GESTURE_COMPLETION_TIMEOUT, MILLISECONDS);
}
public static class GestureDispatchActivity extends AccessibilityTestActivity {
@@ -507,52 +500,6 @@
}
}
- public static class MyGestureCallback extends AccessibilityService.GestureResultCallback {
- private boolean mCompleted;
- private boolean mCancelled;
-
- @Override
- public synchronized void onCompleted(GestureDescription gestureDescription) {
- mCompleted = true;
- notifyAll();
- }
-
- @Override
- public synchronized void onCancelled(GestureDescription gestureDescription) {
- mCancelled = true;
- notifyAll();
- }
-
- public synchronized void assertGestureCompletes(long timeout) {
- if (mCompleted) {
- return;
- }
- try {
- wait(timeout);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- assertTrue("Gesture did not complete. Canceled = " + mCancelled, mCompleted);
- }
-
- public synchronized void assertGestureCancels(long timeout) {
- if (mCancelled) {
- return;
- }
- try {
- wait(timeout);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- assertTrue("Gesture did not cancel. Completed = " + mCompleted, mCancelled);
- }
-
- public synchronized void reset() {
- mCancelled = false;
- mCompleted = false;
- }
- }
-
private void waitForMotionEvents(Matcher<MotionEvent> matcher, int numEventsExpected)
throws InterruptedException {
synchronized (mMotionEvents) {
@@ -597,53 +544,38 @@
}
}
- private GestureDescription createClickInViewBounds(Point clickPoint) {
- Point offsetClick = new Point(clickPoint);
- offsetClick.offset(mViewLocation[0], mViewLocation[1]);
- return createClick(offsetClick);
- }
-
- private GestureDescription createClick(Point clickPoint) {
- return createClick(new PointF(clickPoint.x, clickPoint.y));
- }
-
- private GestureDescription createClick(PointF clickPoint) {
- Path clickPath = new Path();
- clickPath.moveTo(clickPoint.x, clickPoint.y);
- StrokeDescription clickStroke =
- new StrokeDescription(clickPath, 0, ViewConfiguration.getTapTimeout());
- GestureDescription.Builder clickBuilder = new GestureDescription.Builder();
- clickBuilder.addStroke(clickStroke);
- return clickBuilder.build();
- }
-
- private GestureDescription createLongClickInViewBounds(Point clickPoint) {
- Point offsetPoint = new Point(clickPoint);
- offsetPoint.offset(mViewLocation[0], mViewLocation[1]);
- Path clickPath = new Path();
- clickPath.moveTo(offsetPoint.x, offsetPoint.y);
- int longPressTime = ViewConfiguration.getLongPressTimeout();
-
- StrokeDescription longClickStroke =
- new StrokeDescription(clickPath, 0, longPressTime + (longPressTime / 2));
- GestureDescription.Builder longClickBuilder = new GestureDescription.Builder();
- longClickBuilder.addStroke(longClickStroke);
- return longClickBuilder.build();
- }
-
- private GestureDescription createSwipeInViewBounds(Point start, Point end, long duration) {
- return new GestureDescription.Builder().addStroke(
- new StrokeDescription(linePathInViewBounds(start, end), 0, duration, false))
+ private GestureDescription clickWithinView(PointF clickPoint) {
+ return new GestureDescription.Builder()
+ .addStroke(click(withinView(clickPoint)))
.build();
}
- private GestureDescription createPinchInViewBounds(Point centerPoint, int startSpacing,
+ private GestureDescription longClickWithinView(PointF clickPoint) {
+ return new GestureDescription.Builder()
+ .addStroke(longClick(withinView(clickPoint)))
+ .build();
+ }
+
+ private PointF withinView(PointF clickPoint) {
+ return add(clickPoint, mViewLocation[0], mViewLocation[1]);
+ }
+
+ private GestureDescription swipeWithinView(PointF start, PointF end, long duration) {
+ return new GestureDescription.Builder()
+ .addStroke(new StrokeDescription(lineWithinView(start, end), 0, duration))
+ .build();
+ }
+
+ private Path lineWithinView(PointF startPoint, PointF endPoint) {
+ return path(withinView(startPoint), withinView(endPoint));
+ }
+
+ private GestureDescription pinchWithinView(PointF centerPoint, int startSpacing,
int endSpacing, float orientation, long duration) {
if ((startSpacing < 0) || (endSpacing < 0)) {
throw new IllegalArgumentException("Pinch spacing cannot be negative");
}
- Point offsetCenter = new Point(centerPoint);
- offsetCenter.offset(mViewLocation[0], mViewLocation[1]);
+ PointF offsetCenter = withinView(centerPoint);
float[] startPoint1 = new float[2];
float[] endPoint1 = new float[2];
float[] startPoint2 = new float[2];
@@ -675,19 +607,10 @@
path2.moveTo(startPoint2[0], startPoint2[1]);
path2.lineTo(endPoint2[0], endPoint2[1]);
- StrokeDescription path1Stroke = new StrokeDescription(path1, 0, duration);
- StrokeDescription path2Stroke = new StrokeDescription(path2, 0, duration);
- GestureDescription.Builder swipeBuilder = new GestureDescription.Builder();
- swipeBuilder.addStroke(path1Stroke);
- swipeBuilder.addStroke(path2Stroke);
- return swipeBuilder.build();
- }
-
- Path linePathInViewBounds(Point startPoint, Point endPoint) {
- Path path = new Path();
- path.moveTo(startPoint.x + mViewLocation[0], startPoint.y + mViewLocation[1]);
- path.lineTo(endPoint.x + mViewLocation[0], endPoint.y + mViewLocation[1]);
- return path;
+ return new GestureDescription.Builder()
+ .addStroke(new StrokeDescription(path1, 0, duration))
+ .addStroke(new StrokeDescription(path2, 0, duration))
+ .build();
}
private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
@@ -705,13 +628,13 @@
@Override
public void describeTo(Description description) {
- description.appendText("Matching to action " + mAction);
+ description.appendText("Matching to action " + MotionEvent.actionToString(mAction));
}
}
- Matcher<MotionEvent> isAtPoint(final Point point) {
- return isAtPoint(new PointF(point.x, point.y), 0.01f);
+ Matcher<MotionEvent> isAtPoint(final PointF point) {
+ return isAtPoint(point, 0.01f);
}
Matcher<MotionEvent> isAtPoint(final PointF point, final float tol) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index 49c209f..f873849 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -16,7 +16,9 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
+import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
import android.app.Activity;
+import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.Bundle;
import android.os.Handler;
@@ -36,6 +38,7 @@
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Test cases for testing the accessibility APIs for interacting with the soft keyboard show mode.
@@ -70,6 +73,8 @@
private InstrumentedAccessibilityService mService;
private SoftKeyboardController mKeyboardController;
private UiAutomation mUiAutomation;
+ private Activity mActivity;
+ private View mKeyboardTargetView;
private Object mLock = new Object();
@@ -83,7 +88,7 @@
// If we don't call getActivity(), we get an empty list when requesting the number of
// windows on screen.
- getActivity();
+ mActivity = getActivity();
mService = InstrumentedAccessibilityService.enableService(
getInstrumentation(), InstrumentedAccessibilityService.class);
@@ -93,6 +98,8 @@
AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
mUiAutomation.setServiceInfo(info);
+ getInstrumentation().runOnMainSync(
+ () -> mKeyboardTargetView = mActivity.findViewById(R.id.edit_text));
}
@Override
@@ -100,8 +107,11 @@
mKeyboardController.setShowMode(SHOW_MODE_AUTO);
mService.runOnServiceSync(() -> mService.disableSelf());
Activity activity = getActivity();
- activity.getSystemService(InputMethodManager.class)
- .hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
+ View currentFocus = activity.getCurrentFocus();
+ if (currentFocus != null) {
+ activity.getSystemService(InputMethodManager.class)
+ .hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
+ }
}
public void testApiReturnValues_shouldChangeValueOnRequestAndSendCallback() throws Exception {
@@ -228,20 +238,24 @@
*/
private boolean tryShowSoftInput() throws Exception {
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);
+ final AtomicBoolean showSoftInputResult = new AtomicBoolean(false);
+ final Activity activity = getActivity();
+ final ResultReceiver resultReceiver =
+ new ResultReceiver(new Handler(activity.getMainLooper())) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ queue.add(resultCode);
+ }
+ };
- getInstrumentation().runOnMainSync(() -> {
- Activity activity = getActivity();
- ResultReceiver resultReceiver =
- new ResultReceiver(new Handler(activity.getMainLooper())) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- queue.add(resultCode);
- }
- };
- View editText = activity.findViewById(R.id.edit_text);
- activity.getSystemService(InputMethodManager.class)
- .showSoftInput(editText, InputMethodManager.SHOW_FORCED, resultReceiver);
- });
+ final Instrumentation instrumentation = getInstrumentation();
+ instrumentation.runOnMainSync(() -> mKeyboardTargetView.requestFocus());
+ instrumentation.waitForIdleSync();
+ final InputMethodManager imm = mActivity.getSystemService(InputMethodManager.class);
+ instrumentation.runOnMainSync(() -> showSoftInputResult.set(imm.showSoftInput(
+ mKeyboardTargetView, InputMethodManager.SHOW_FORCED, resultReceiver)));
+
+ assertTrue("InputMethodManager refused to show a soft input", showSoftInputResult.get());
Integer result;
try {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTestActivity.java
deleted file mode 100644
index 42a4375..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTestActivity.java
+++ /dev/null
@@ -1,31 +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 android.accessibilityservice.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public abstract class AccessibilityTestActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index abc759c..38bb738 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -14,10 +14,10 @@
package android.accessibilityservice.cts;
+import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
import android.app.UiAutomation;
import android.graphics.RectF;
import android.os.Bundle;
-import android.os.Debug;
import android.os.Message;
import android.os.Parcelable;
import android.text.SpannableString;
@@ -52,7 +52,6 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Test cases for actions taken on text views.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java
deleted file mode 100644
index 2b93a08..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (C) 2012 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.accessibilityservice.cts;
-
-import android.os.Bundle;
-
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity for testing the accessibility APIs for traversing the
- * text content of a View at several granularities.
- */
-public class AccessibilityTextTraversalActivity extends AccessibilityTestActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.accessibility_text_traversal_test);
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 6bc2969..646d9c3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -14,6 +14,7 @@
package android.accessibilityservice.cts;
+import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
import android.app.UiAutomation;
import android.content.pm.PackageManager;
import android.os.Bundle;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingActivity.java
deleted file mode 100644
index c28e7e8..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Copyright (C) 2012 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.accessibilityservice.cts;
-
-import android.os.Bundle;
-
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity for testing the accessibility focus APIs exposed to
- * accessibility services. These APIs allow moving accessibility
- * focus in the view tree from an AccessiiblityService. Specifically,
- * this activity is for verifying the hierarchical movement of the
- * accessibility focus.
- */
-public class AccessibilityViewTreeReportingActivity extends AccessibilityTestActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.accessibility_view_tree_reporting_test);
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
index 305050b..d8094e0 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -14,10 +14,22 @@
package android.accessibilityservice.cts;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityViewTreeReportingActivity;
+import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -28,100 +40,124 @@
import android.widget.Button;
import android.widget.LinearLayout;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Test cases for testing the accessibility focus APIs exposed to accessibility
* services. This test checks how the view hierarchy is reported to accessibility
* services.
*/
-public class AccessibilityViewTreeReportingTest
- extends AccessibilityActivityTestCase<AccessibilityViewTreeReportingActivity>{
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityViewTreeReportingTest {
+ private static final int TIMEOUT_ASYNC_PROCESSING = 5000;
- public AccessibilityViewTreeReportingTest() {
- super(AccessibilityViewTreeReportingActivity.class);
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private AccessibilityViewTreeReportingActivity mActivity;
+
+ @Rule
+ public ActivityTestRule<AccessibilityViewTreeReportingActivity> mActivityRule =
+ new ActivityTestRule<>(AccessibilityViewTreeReportingActivity.class, false, false);
+
+ @BeforeClass
+ public static void oneTimeSetup() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation();
+ AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ sUiAutomation.setServiceInfo(info);
}
- @MediumTest
+ @AfterClass
+ public static void finalTearDown() throws Exception {
+ sUiAutomation.destroy();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = (AccessibilityViewTreeReportingActivity) launchActivityAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, mActivityRule);
+ setGetNonImportantViews(false);
+ }
+
+
+ @Test
public void testDescendantsOfNotImportantViewReportedInOrder1() throws Exception {
- UiAutomation uiAutomation = getUiAutomation(false);
- AccessibilityNodeInfo firstFrameLayout =
- getNodeByText(uiAutomation, R.string.firstFrameLayout);
+ AccessibilityNodeInfo firstFrameLayout = getNodeByText(R.string.firstFrameLayout);
assertNotNull(firstFrameLayout);
assertSame(3, firstFrameLayout.getChildCount());
// Check if the first child is the right one.
- AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
+ AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
assertEquals(firstTextView, firstFrameLayout.getChild(0));
// Check if the second child is the right one.
- AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
+ AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
assertEquals(firstEditText, firstFrameLayout.getChild(1));
// Check if the third child is the right one.
- AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+ AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
assertEquals(firstButton, firstFrameLayout.getChild(2));
}
- @MediumTest
+ @Test
public void testDescendantsOfNotImportantViewReportedInOrder2() throws Exception {
- UiAutomation uiAutomation = getUiAutomation(false);
- AccessibilityNodeInfo secondFrameLayout =
- getNodeByText(uiAutomation, R.string.secondFrameLayout);
+ AccessibilityNodeInfo secondFrameLayout = getNodeByText(R.string.secondFrameLayout);
assertNotNull(secondFrameLayout);
assertSame(3, secondFrameLayout.getChildCount());
// Check if the first child is the right one.
- AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
+ AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
assertEquals(secondTextView, secondFrameLayout.getChild(0));
// Check if the second child is the right one.
- AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
+ AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
assertEquals(secondEditText, secondFrameLayout.getChild(1));
// Check if the third child is the right one.
- AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
+ AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
assertEquals(secondButton, secondFrameLayout.getChild(2));
}
- @MediumTest
+ @Test
public void testDescendantsOfNotImportantViewReportedInOrder3() throws Exception {
- UiAutomation uiAutomation = getUiAutomation(false);
AccessibilityNodeInfo rootLinearLayout =
- getNodeByText(uiAutomation, R.string.rootLinearLayout);
+ getNodeByText(R.string.rootLinearLayout);
assertNotNull(rootLinearLayout);
assertSame(4, rootLinearLayout.getChildCount());
// Check if the first child is the right one.
AccessibilityNodeInfo firstFrameLayout =
- getNodeByText(uiAutomation, R.string.firstFrameLayout);
+ getNodeByText(R.string.firstFrameLayout);
assertEquals(firstFrameLayout, rootLinearLayout.getChild(0));
// Check if the second child is the right one.
- AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
+ AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
assertEquals(secondTextView, rootLinearLayout.getChild(1));
// Check if the third child is the right one.
- AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
+ AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
assertEquals(secondEditText, rootLinearLayout.getChild(2));
// Check if the fourth child is the right one.
- AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
+ AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
assertEquals(secondButton, rootLinearLayout.getChild(3));
}
- @MediumTest
+ @Test
public void testDrawingOrderInImportantParentFollowsXmlOrder() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- getActivity().findViewById(R.id.firstLinearLayout)
- .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
- });
+ sInstrumentation.runOnMainSync(() -> mActivity.findViewById(R.id.firstLinearLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES));
- UiAutomation uiAutomation = getUiAutomation(false);
- AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
- AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
- AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+ AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+ AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+ AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
// Drawing order is: firstTextView, firstEditText, firstButton
assertTrue(firstTextView.getDrawingOrder() < firstEditText.getDrawingOrder());
@@ -133,202 +169,160 @@
assertTrue(copyOfFirstEditText.getDrawingOrder() < firstButton.getDrawingOrder());
}
- @MediumTest
+ @Test
public void testDrawingOrderGettingAllViewsFollowsXmlOrder() throws Exception {
- UiAutomation uiAutomation = getUiAutomation(true);
- AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
- AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
- AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+ setGetNonImportantViews(true);
+ AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+ AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+ AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
// Drawing order is: firstTextView, firstEditText, firstButton
assertTrue(firstTextView.getDrawingOrder() < firstEditText.getDrawingOrder());
assertTrue(firstEditText.getDrawingOrder() < firstButton.getDrawingOrder());
}
- @MediumTest
+ @Test
public void testDrawingOrderWithZCoordsDrawsHighestZLast() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- AccessibilityViewTreeReportingActivity activity = getActivity();
- activity.findViewById(R.id.firstTextView).setZ(50);
- activity.findViewById(R.id.firstEditText).setZ(100);
- }
+ setGetNonImportantViews(true);
+ sInstrumentation.runOnMainSync(() -> {
+ mActivity.findViewById(R.id.firstTextView).setZ(50);
+ mActivity.findViewById(R.id.firstEditText).setZ(100);
});
- UiAutomation uiAutomation = getUiAutomation(true);
- AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
- AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
- AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+ AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+ AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+ AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
// Drawing order is firstButton (no z), firstTextView (z=50), firstEditText (z=100)
assertTrue(firstButton.getDrawingOrder() < firstTextView.getDrawingOrder());
assertTrue(firstTextView.getDrawingOrder() < firstEditText.getDrawingOrder());
}
- @MediumTest
+ @Test
public void testDrawingOrderWithCustomDrawingOrder() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- // Reorganize the hiearchy to replace firstLinearLayout with one that allows us to
- // control the draw order
- AccessibilityViewTreeReportingActivity activity = getActivity();
- LinearLayout rootLinearLayout =
- (LinearLayout) activity.findViewById(R.id.rootLinearLayout);
- LinearLayout firstLinearLayout =
- (LinearLayout) activity.findViewById(R.id.firstLinearLayout);
- View firstTextView = activity.findViewById(R.id.firstTextView);
- View firstEditText = activity.findViewById(R.id.firstEditText);
- View firstButton = activity.findViewById(R.id.firstButton);
- firstLinearLayout.removeAllViews();
- LinearLayoutWithDrawingOrder layoutWithDrawingOrder =
- new LinearLayoutWithDrawingOrder(activity);
- rootLinearLayout.addView(layoutWithDrawingOrder);
- layoutWithDrawingOrder.addView(firstTextView);
- layoutWithDrawingOrder.addView(firstEditText);
- layoutWithDrawingOrder.addView(firstButton);
- layoutWithDrawingOrder.childDrawingOrder = new int[] {2, 0, 1};
- }
+ setGetNonImportantViews(true);
+ sInstrumentation.runOnMainSync(() -> {
+ // Reorganize the hiearchy to replace firstLinearLayout with one that allows us to
+ // control the draw order
+ LinearLayout rootLinearLayout =
+ (LinearLayout) mActivity.findViewById(R.id.rootLinearLayout);
+ LinearLayout firstLinearLayout =
+ (LinearLayout) mActivity.findViewById(R.id.firstLinearLayout);
+ View firstTextView = mActivity.findViewById(R.id.firstTextView);
+ View firstEditText = mActivity.findViewById(R.id.firstEditText);
+ View firstButton = mActivity.findViewById(R.id.firstButton);
+ firstLinearLayout.removeAllViews();
+ LinearLayoutWithDrawingOrder layoutWithDrawingOrder =
+ new LinearLayoutWithDrawingOrder(mActivity);
+ rootLinearLayout.addView(layoutWithDrawingOrder);
+ layoutWithDrawingOrder.addView(firstTextView);
+ layoutWithDrawingOrder.addView(firstEditText);
+ layoutWithDrawingOrder.addView(firstButton);
+ layoutWithDrawingOrder.childDrawingOrder = new int[] {2, 0, 1};
});
- UiAutomation uiAutomation = getUiAutomation(true);
- AccessibilityNodeInfo firstTextView = getNodeByText(uiAutomation, R.string.firstTextView);
- AccessibilityNodeInfo firstEditText = getNodeByText(uiAutomation, R.string.firstEditText);
- AccessibilityNodeInfo firstButton = getNodeByText(uiAutomation, R.string.firstButton);
+ AccessibilityNodeInfo firstTextView = getNodeByText(R.string.firstTextView);
+ AccessibilityNodeInfo firstEditText = getNodeByText(R.string.firstEditText);
+ AccessibilityNodeInfo firstButton = getNodeByText(R.string.firstButton);
// Drawing order is firstEditText, firstButton, firstTextView
assertTrue(firstEditText.getDrawingOrder() < firstButton.getDrawingOrder());
assertTrue(firstButton.getDrawingOrder() < firstTextView.getDrawingOrder());
}
- @MediumTest
+ @Test
public void testDrawingOrderWithNotImportantSiblingConsidersItsChildren() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- // Make the first frame layout a higher Z so it's drawn last
- getActivity().findViewById(R.id.firstFrameLayout).setZ(100);
- }
- });
- UiAutomation uiAutomation = getUiAutomation(false);
- AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
- AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
- AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
- AccessibilityNodeInfo firstFrameLayout =
- getNodeByText(uiAutomation, R.string.firstFrameLayout);
+ // Make the first frame layout a higher Z so it's drawn last
+ sInstrumentation.runOnMainSync(
+ () -> mActivity.findViewById(R.id.firstFrameLayout).setZ(100));
+ AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
+ AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
+ AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
+ AccessibilityNodeInfo firstFrameLayout = getNodeByText( R.string.firstFrameLayout);
assertTrue(secondTextView.getDrawingOrder() < firstFrameLayout.getDrawingOrder());
assertTrue(secondEditText.getDrawingOrder() < firstFrameLayout.getDrawingOrder());
assertTrue(secondButton.getDrawingOrder() < firstFrameLayout.getDrawingOrder());
}
- @MediumTest
+ @Test
public void testDrawingOrderWithNotImportantParentConsidersParentSibling() throws Exception {
- UiAutomation uiAutomation = getUiAutomation(false);
- AccessibilityNodeInfo firstFrameLayout =
- getNodeByText(uiAutomation, R.string.firstFrameLayout);
- AccessibilityNodeInfo secondTextView = getNodeByText(uiAutomation, R.string.secondTextView);
- AccessibilityNodeInfo secondEditText = getNodeByText(uiAutomation, R.string.secondEditText);
- AccessibilityNodeInfo secondButton = getNodeByText(uiAutomation, R.string.secondButton);
+ AccessibilityNodeInfo firstFrameLayout = getNodeByText(R.string.firstFrameLayout);
+ AccessibilityNodeInfo secondTextView = getNodeByText(R.string.secondTextView);
+ AccessibilityNodeInfo secondEditText = getNodeByText(R.string.secondEditText);
+ AccessibilityNodeInfo secondButton = getNodeByText(R.string.secondButton);
assertTrue(secondTextView.getDrawingOrder() > firstFrameLayout.getDrawingOrder());
assertTrue(secondEditText.getDrawingOrder() > firstFrameLayout.getDrawingOrder());
assertTrue(secondButton.getDrawingOrder() > firstFrameLayout.getDrawingOrder());
}
- @MediumTest
+ @Test
public void testDrawingOrderRootNodeHasIndex0() throws Exception {
- assertEquals(0, getUiAutomation(false).getRootInActiveWindow().getDrawingOrder());
+ assertEquals(0, sUiAutomation.getRootInActiveWindow().getDrawingOrder());
}
- @MediumTest
+ @Test
public void testAccessibilityImportanceReportingForImportantView() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- // Manually control importance for firstButton
- AccessibilityViewTreeReportingActivity activity = getActivity();
- View firstButton = activity.findViewById(R.id.firstButton);
- firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
+ setGetNonImportantViews(true);
+ sInstrumentation.runOnMainSync(() -> {
+ // Manually control importance for firstButton
+ View firstButton = mActivity.findViewById(R.id.firstButton);
+ firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
});
- UiAutomation uiAutomation = getUiAutomation(true);
- AccessibilityNodeInfo firstButtonNode = getNodeByText(uiAutomation, R.string.firstButton);
+ AccessibilityNodeInfo firstButtonNode = getNodeByText(R.string.firstButton);
assertTrue(firstButtonNode.isImportantForAccessibility());
}
- @MediumTest
+ @Test
public void testAccessibilityImportanceReportingForUnimportantView() throws Exception {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- // Manually control importance for firstButton
- AccessibilityViewTreeReportingActivity activity = getActivity();
- View firstButton = activity.findViewById(R.id.firstButton);
- firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
+ setGetNonImportantViews(true);
+ sInstrumentation.runOnMainSync(() -> {
+ View firstButton = mActivity.findViewById(R.id.firstButton);
+ firstButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
});
- UiAutomation uiAutomation = getUiAutomation(true);
- AccessibilityNodeInfo firstButtonNode = getNodeByText(uiAutomation, R.string.firstButton);
+ AccessibilityNodeInfo firstButtonNode = getNodeByText(R.string.firstButton);
assertFalse(firstButtonNode.isImportantForAccessibility());
}
- @MediumTest
+ @Test
public void testAddViewToLayout_receiveSubtreeEvent() throws Throwable {
final LinearLayout layout =
- (LinearLayout) getActivity().findViewById(R.id.secondLinearLayout);
- final Button newButton = new Button(getActivity());
+ (LinearLayout) mActivity.findViewById(R.id.secondLinearLayout);
+ final Button newButton = new Button(mActivity);
newButton.setText("New Button");
newButton.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
newButton.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
AccessibilityEvent awaitedEvent =
- getInstrumentation().getUiAutomation().executeAndWaitForEvent(
- new Runnable() {
- @Override
- public void run() {
- // trigger the event
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- layout.addView(newButton);
- }
- });
- }},
- new UiAutomation.AccessibilityEventFilter() {
- // check the received event
- @Override
- public boolean accept(AccessibilityEvent event) {
- boolean isContentChanged = event.getEventType()
- == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
- int isSubTree = (event.getContentChangeTypes()
- & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
- boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
- getActivity().getPackageName());
- return isContentChanged && (isSubTree != 0) && isFromThisPackage;
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ sUiAutomation.executeAndWaitForEvent(
+ () -> mActivity.runOnUiThread(() -> layout.addView(newButton)),
+ (event) -> {
+ boolean isContentChanged = event.getEventType()
+ == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+ int isSubTree = (event.getContentChangeTypes()
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
+ mActivity.getPackageName());
+ return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+ }, TIMEOUT_ASYNC_PROCESSING);
// The event should come from a view that's important for accessibility, even though the
// layout we added it to isn't important. Otherwise services may not find out about the
// new button.
assertTrue(awaitedEvent.getSource().isImportantForAccessibility());
}
- private UiAutomation getUiAutomation(boolean getNonImportantViews) {
- UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- AccessibilityServiceInfo serviceInfo = uiAutomation.getServiceInfo();
+ private void setGetNonImportantViews(boolean getNonImportantViews) {
+ AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo();
serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
serviceInfo.flags |= getNonImportantViews ?
AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
- uiAutomation.setServiceInfo(serviceInfo);
- return uiAutomation;
+ sUiAutomation.setServiceInfo(serviceInfo);
}
- private AccessibilityNodeInfo getNodeByText(UiAutomation uiAutomation, int stringId) {
- return uiAutomation.getRootInActiveWindow()
- .findAccessibilityNodeInfosByText(getString(stringId)).get(0);
+ private AccessibilityNodeInfo getNodeByText(int stringId) {
+ return sUiAutomation.getRootInActiveWindow().findAccessibilityNodeInfosByText(
+ sInstrumentation.getContext().getString(stringId)).get(0);
}
class LinearLayoutWithDrawingOrder extends LinearLayout {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
index b67fc28..8b1154e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
@@ -63,16 +63,22 @@
if (mSingleVolume) {
return;
}
- int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
- int otherVolume = (startingVolume == 0) ? 1 : startingVolume - 1;
- InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
- mInstrumentation, InstrumentedAccessibilityService.class);
-
- service.runOnServiceSync(() ->
- mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume, 0));
- assertEquals("Accessibility service should be able to change accessibility volume",
- otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
- service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
- AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
+ final int startingVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY);
+ final int otherVolume = (startingVolume == 0) ? 1 : startingVolume - 1;
+ final InstrumentedAccessibilityService service = InstrumentedAccessibilityService
+ .enableService(mInstrumentation, InstrumentedAccessibilityService.class);
+ try {
+ service.runOnServiceSync(() ->
+ mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume,
+ 0));
+ assertEquals("Accessibility service should be able to change accessibility volume",
+ otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
+ service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
+ AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
+ } finally {
+ if (service != null) {
+ service.runOnServiceSync(() -> service.disableSelf());
+ }
+ }
}
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
deleted file mode 100644
index 9070a81..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice.cts;
-
-import android.os.Bundle;
-import android.view.View;
-
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.accessibilityservice.cts.R;
-
-/**
- * Activity for testing the accessibility APIs for querying of
- * the screen content. These APIs allow exploring the screen and
- * requesting an action to be performed on a given view from an
- * AccessibilityService.
- */
-public class AccessibilityWindowQueryActivity extends AccessibilityTestActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.query_window_test);
-
- findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- /* do nothing */
- }
- });
- findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
- public boolean onLongClick(View v) {
- return true;
- }
- });
-
- findViewById(R.id.button5).setAccessibilityDelegate(new View.AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- info.addAction(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action == R.id.foo_custom_action) {
- return true;
- }
- return super.performAccessibilityAction(host, action, args);
- }
- });
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 31292c3..0859e8a 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -16,7 +16,17 @@
package android.accessibilityservice.cts;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
+import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
@@ -26,14 +36,15 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
import android.app.UiAutomation;
+import android.app.UiAutomation.AccessibilityEventFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Rect;
import android.test.suitebuilder.annotation.MediumTest;
import android.view.Gravity;
import android.view.View;
-import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -59,23 +70,16 @@
private static String CONTENT_VIEW_RES_NAME =
"android.accessibilityservice.cts:id/added_content";
private static final long TIMEOUT_WINDOW_STATE_IDLE = 500;
- private final UiAutomation.AccessibilityEventFilter mWindowsChangedFilter =
- new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED);
- }
- };
- private final UiAutomation.AccessibilityEventFilter mDividerPresentFilter =
- new UiAutomation.AccessibilityEventFilter() {
+ private final AccessibilityEventFilter mDividerPresentFilter =
+ new AccessibilityEventFilter() {
@Override
public boolean accept(AccessibilityEvent event) {
return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED &&
isDividerWindowPresent(getInstrumentation().getUiAutomation()) );
}
};
- private final UiAutomation.AccessibilityEventFilter mDividerAbsentFilter =
- new UiAutomation.AccessibilityEventFilter() {
+ private final AccessibilityEventFilter mDividerAbsentFilter =
+ new AccessibilityEventFilter() {
@Override
public boolean accept(AccessibilityEvent event) {
return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED &&
@@ -91,13 +95,8 @@
public void testFindByText() throws Throwable {
// First, make the root view of the activity an accessibility node. This allows us to
// later exclude views that are part of the activity's DecorView.
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- getActivity().findViewById(R.id.added_content)
- .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
- });
+ runTestOnUiThread(() -> getActivity().findViewById(R.id.added_content)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES));
// Start looking from the added content instead of from the root accessibility node so
// that nodes that we don't expect (i.e. window control buttons) are not included in the
@@ -142,30 +141,12 @@
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/button1").get(0);
- // Argh...
- final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
-
- // Click the button.
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- }
- },
- new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
- events.add(event);
- return true;
- }
- return false;
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ // Click the button to generate an event
+ AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+ () -> button1.performAction(ACTION_CLICK),
+ filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
// Make sure the source window cannot be accessed.
- AccessibilityEvent event = events.get(0);
assertNull(event.getSource().getWindow());
}
@@ -214,30 +195,12 @@
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/button1").get(0);
- // Argh...
- final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
-
// Click the button.
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- }
- },
- new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
- events.add(event);
- return true;
- }
- return false;
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+ () -> button1.performAction(ACTION_CLICK),
+ filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
// Get the source window.
- AccessibilityEvent event = events.get(0);
AccessibilityWindowInfo window = event.getSource().getWindow();
// Verify the application window.
@@ -269,30 +232,12 @@
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/button1").get(0);
- // Argh...
- final List<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
-
// Click the button.
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- button1.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- }
- },
- new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
- events.add(event);
- return true;
- }
- return false;
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
+ () -> button1.performAction(ACTION_CLICK),
+ filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
// Get the source window.
- AccessibilityEvent event = events.get(0);
AccessibilityWindowInfo window = event.getSource().getWindow();
// Find a another button from the event's window.
@@ -301,19 +246,8 @@
"android.accessibilityservice.cts:id/button2").get(0);
// Click the second button.
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- button2.performAction(AccessibilityNodeInfo.ACTION_CLICK);
- }
- },
- new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED;
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ uiAutomation.executeAndWaitForEvent(() -> button2.performAction(ACTION_CLICK),
+ filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
} finally {
clearAccessInteractiveWindowsFlag();
}
@@ -324,22 +258,34 @@
setAccessInteractiveWindowsFlag();
try {
// Add two more windows.
- addTwoAppPanelWindows();
+ final View views[];
+ views = addTwoAppPanelWindows();
- // Put accessibility focus in the first app window.
- ensureAppWindowFocusedOrFail(0);
- // Make sure there only one accessibility focus.
- assertSingleAccessibilityFocus();
+ try {
+ // Put accessibility focus in the first app window.
+ ensureAppWindowFocusedOrFail(0);
+ // Make sure there only one accessibility focus.
+ assertSingleAccessibilityFocus();
- // Put accessibility focus in the second app window.
- ensureAppWindowFocusedOrFail(1);
- // Make sure there only one accessibility focus.
- assertSingleAccessibilityFocus();
+ // Put accessibility focus in the second app window.
+ ensureAppWindowFocusedOrFail(1);
+ // Make sure there only one accessibility focus.
+ assertSingleAccessibilityFocus();
- // Put accessibility focus in the third app window.
- ensureAppWindowFocusedOrFail(2);
- // Make sure there only one accessibility focus.
- assertSingleAccessibilityFocus();
+ // Put accessibility focus in the third app window.
+ ensureAppWindowFocusedOrFail(2);
+ // Make sure there only one accessibility focus.
+ assertSingleAccessibilityFocus();
+ } finally {
+ // Clean up panel windows
+ getInstrumentation().runOnMainSync(() -> {
+ WindowManager wm =
+ getInstrumentation().getContext().getSystemService(WindowManager.class);
+ for (View view : views) {
+ wm.removeView(view);
+ }
+ });
+ }
} finally {
ensureAccessibilityFocusCleared();
clearAccessInteractiveWindowsFlag();
@@ -347,24 +293,7 @@
}
@MediumTest
- public void testPerformActionFocus() throws Exception {
- // find a view and make sure it is not focused
- AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
- .getRootInActiveWindow().findAccessibilityNodeInfosByText(
- getString(R.string.button5)).get(0);
- assertFalse(button.isFocused());
-
- // focus the view
- assertTrue(button.performAction(ACTION_FOCUS));
-
- // find the view again and make sure it is focused
- button = getInstrumentation().getUiAutomation().getRootInActiveWindow()
- .findAccessibilityNodeInfosByText(getString(R.string.button5)).get(0);
- assertTrue(button.isFocused());
- }
-
- @MediumTest
- public void testPerformActionClearFocus() throws Exception {
+ public void testPerformActionSetAndClearFocus() throws Exception {
// find a view and make sure it is not focused
AccessibilityNodeInfo button = getInstrumentation().getUiAutomation()
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
@@ -439,20 +368,10 @@
getString(R.string.button5)).get(0);
assertFalse(button.isSelected());
- // Make an action and wait for an event.
- AccessibilityEvent expected = getInstrumentation().getUiAutomation()
- .executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- button.performAction(ACTION_CLICK);
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED);
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ // Perform an action and wait for an event
+ AccessibilityEvent expected = getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+ () -> button.performAction(ACTION_CLICK),
+ filterForEventType(TYPE_VIEW_CLICKED), TIMEOUT_ASYNC_PROCESSING);
// Make sure the expected event was received.
assertNotNull(expected);
@@ -466,20 +385,10 @@
getString(R.string.button5)).get(0);
assertFalse(button.isSelected());
- // Make an action and wait for an event.
- AccessibilityEvent expected = getInstrumentation().getUiAutomation()
- .executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- button.performAction(ACTION_LONG_CLICK);
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ // Perform an action and wait for an event.
+ AccessibilityEvent expected = getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+ () -> button.performAction(ACTION_LONG_CLICK),
+ filterForEventType(TYPE_VIEW_LONG_CLICKED), TIMEOUT_ASYNC_PROCESSING);
// Make sure the expected event was received.
assertNotNull(expected);
@@ -517,20 +426,8 @@
// focus and wait for the event
AccessibilityEvent awaitedEvent = getInstrumentation().getUiAutomation()
- .executeAndWaitForEvent(
- new Runnable() {
- @Override
- public void run() {
- assertTrue(button.performAction(ACTION_FOCUS));
- }
- },
- new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- },
- TIMEOUT_ASYNC_PROCESSING);
+ .executeAndWaitForEvent(() -> button.performAction(ACTION_FOCUS),
+ filterForEventType(TYPE_VIEW_FOCUSED), TIMEOUT_ASYNC_PROCESSING);
assertNotNull(awaitedEvent);
@@ -627,13 +524,8 @@
setAccessInteractiveWindowsFlag();
final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
assertFalse(isDividerWindowPresent(uiAutomation));
- Runnable toggleSplitScreenRunnable = new Runnable() {
- @Override
- public void run() {
- assertTrue(uiAutomation.performGlobalAction(
+ Runnable toggleSplitScreenRunnable = () -> assertTrue(uiAutomation.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN));
- }
- };
uiAutomation.executeAndWaitForEvent(toggleSplitScreenRunnable, mDividerPresentFilter,
TIMEOUT_ASYNC_PROCESSING);
@@ -652,7 +544,7 @@
getInstrumentation().runOnMainSync(() -> {
getActivity().enterPictureInPictureMode();
});
- }, mWindowsChangedFilter, TIMEOUT_ASYNC_PROCESSING);
+ }, filterForEventType(TYPE_WINDOWS_CHANGED), TIMEOUT_ASYNC_PROCESSING);
waitForIdle();
// We should be able to find a picture-in-picture window now
@@ -673,8 +565,6 @@
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = windows.get(i);
- Rect bounds = new Rect();
- window.getBoundsInScreen(bounds);
if (window.getType() == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER) {
return true;
}
@@ -704,8 +594,11 @@
throw new AssertionError("Duplicate accessibility focus");
}
} else {
- assertNull(window.getRoot().findFocus(
- AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ AccessibilityNodeInfo root = window.getRoot();
+ if (root != null) {
+ assertNull(root.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY));
+ }
}
}
}
@@ -713,7 +606,7 @@
private void ensureAppWindowFocusedOrFail(int appWindowIndex) throws TimeoutException {
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
- AccessibilityWindowInfo focusTareget = null;
+ AccessibilityWindowInfo focusTarget = null;
int visitedAppWindows = -1;
final int windowCount = windows.size();
@@ -722,118 +615,81 @@
if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) {
visitedAppWindows++;
if (appWindowIndex <= visitedAppWindows) {
- focusTareget = window;
+ focusTarget = window;
break;
}
}
}
- if (focusTareget == null) {
+ if (focusTarget == null) {
throw new IllegalStateException("Couldn't find app window: " + appWindowIndex);
}
- if (focusTareget.isAccessibilityFocused()) {
+ if (focusTarget.isAccessibilityFocused()) {
return;
}
- final AccessibilityWindowInfo finalFocusTarget = focusTareget;
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- assertTrue(finalFocusTarget.getRoot().performAction(
- AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS));
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
- }
- }, TIMEOUT_ASYNC_PROCESSING);
+ final AccessibilityWindowInfo finalFocusTarget = focusTarget;
+ uiAutomation.executeAndWaitForEvent(() -> assertTrue(finalFocusTarget.getRoot()
+ .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+ TIMEOUT_ASYNC_PROCESSING);
windows = uiAutomation.getWindows();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = windows.get(i);
- if (window.getId() == focusTareget.getId()) {
+ if (window.getId() == focusTarget.getId()) {
assertTrue(window.isAccessibilityFocused());
break;
}
}
}
- private void addTwoAppPanelWindows() throws TimeoutException {
+ private View[] addTwoAppPanelWindows() throws TimeoutException {
final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
uiAutomation.waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
+ final View views[] = new View[2];
// Add the first window.
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.gravity = Gravity.TOP;
- params.y = getStatusBarHeight();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
- params.token = getActivity().getWindow().getDecorView().getWindowToken();
+ uiAutomation.executeAndWaitForEvent(() -> getInstrumentation().runOnMainSync(() -> {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.gravity = Gravity.TOP;
+ params.y = getStatusBarHeight(getActivity());
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ params.token = getActivity().getWindow().getDecorView().getWindowToken();
- Button button = new Button(getActivity());
- button.setText(R.string.button1);
- getActivity().getWindowManager().addView(button, params);
- }
- });
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
- }
- }, TIMEOUT_ASYNC_PROCESSING);
+ final Button button = new Button(getActivity());
+ button.setText(R.string.button1);
+ views[0] = button;
+ getActivity().getWindowManager().addView(button, params);
+ }), filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), TIMEOUT_ASYNC_PROCESSING);
// Add the second window.
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.gravity = Gravity.BOTTOM;
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
- params.token = getActivity().getWindow().getDecorView().getWindowToken();
+ uiAutomation.executeAndWaitForEvent(() -> getInstrumentation().runOnMainSync(() -> {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.gravity = Gravity.BOTTOM;
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ params.token = getActivity().getWindow().getDecorView().getWindowToken();
- Button button = new Button(getActivity());
- button.setText(R.string.button2);
- getActivity().getWindowManager().addView(button, params);
- }
- });
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
- }
- }, TIMEOUT_ASYNC_PROCESSING);
- }
-
- private int getStatusBarHeight() {
- final Rect rect = new Rect();
- Window window = getActivity().getWindow();
- window.getDecorView().getWindowVisibleDisplayFrame(rect);
- return rect.top;
+ final Button button = new Button(getActivity());
+ button.setText(R.string.button2);
+ views[1] = button;
+ getActivity().getWindowManager().addView(button, params);
+ }), filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), TIMEOUT_ASYNC_PROCESSING);
+ return views;
}
private void setAccessInteractiveWindowsFlag () {
@@ -853,26 +709,20 @@
private void ensureAccessibilityFocusCleared() {
try {
final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- uiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
- final int windowCount = windows.size();
- for (int i = 0; i < windowCount; i++) {
- AccessibilityWindowInfo window = windows.get(i);
- if (window.isAccessibilityFocused()) {
- window.getRoot().performAction(
+ uiAutomation.executeAndWaitForEvent(() -> {
+ List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = windows.get(i);
+ if (window.isAccessibilityFocused()) {
+ AccessibilityNodeInfo root = window.getRoot();
+ if (root != null) {
+ root.performAction(
AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
}
}
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return event.getEventType() ==
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
- }
- }, TIMEOUT_ASYNC_PROCESSING);
+ }, filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED), TIMEOUT_ASYNC_PROCESSING);
} catch (TimeoutException te) {
/* ignore */
}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingActivity.java
deleted file mode 100644
index c638951..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice.cts;
-
-import android.os.Bundle;
-
-/**
- * Activity used by ActivityWindowReportingTest
- */
-public class AccessibilityWindowReportingActivity extends AccessibilityTestActivity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.accessibility_window_reporting_test);
- setTitle("AccessibilityWindowReportingActivity");
- }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
index c9eef93..2977843 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -16,80 +16,253 @@
package android.accessibilityservice.cts;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_PIP;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_REMOVED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_TITLE;
+
+import static junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
+import android.app.Activity;
+import android.app.Instrumentation;
import android.app.UiAutomation;
-import android.text.TextUtils;
+import android.os.Debug;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
- * Tests that AccessibilityWindowInfos are properly populated
+ * Tests that window changes produce the correct events and that AccessibilityWindowInfos are
+ * properly populated
*/
-public class AccessibilityWindowReportingTest
- extends AccessibilityActivityTestCase<AccessibilityWindowReportingActivity> {
- UiAutomation mUiAutomation;
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityWindowReportingTest {
+ private static final int TIMEOUT_ASYNC_PROCESSING = 5000;
+ private static final CharSequence TOP_WINDOW_TITLE =
+ "android.accessibilityservice.cts.AccessibilityWindowReportingTest.TOP_WINDOW_TITLE";
- public AccessibilityWindowReportingTest() {
- super(AccessibilityWindowReportingActivity.class);
- }
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+ private Activity mActivity;
+ private CharSequence mActivityTitle;
- public void setUp() throws Exception {
- super.setUp();
- mUiAutomation = getInstrumentation().getUiAutomation();
- AccessibilityServiceInfo info = mUiAutomation.getServiceInfo();
+ @Rule
+ public ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
+ new ActivityTestRule<>(AccessibilityWindowReportingActivity.class, false, false);
+
+ @BeforeClass
+ public static void oneTimeSetup() throws Exception {
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation();
+ AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
- mUiAutomation.setServiceInfo(info);
+ sUiAutomation.setServiceInfo(info);
}
- public void tearDown() throws Exception {
- mUiAutomation.destroy();
- super.tearDown();
+ @AfterClass
+ public static void finalTearDown() throws Exception {
+ sUiAutomation.destroy();
}
- public void testWindowTitle_getTitleReturnsTitle() {
- AccessibilityWindowInfo window = findWindowByTitle(getActivity().getTitle());
- assertNotNull("Window title not reported to accessibility", window);
- window.recycle();
+ @Before
+ public void setUp() throws Exception {
+ mActivity = launchActivityAndWaitForItToBeOnscreen(
+ sInstrumentation, sUiAutomation, mActivityRule);
+ mActivityTitle = getActivityTitle(sInstrumentation, mActivity);
}
+ @Test
public void testUpdatedWindowTitle_generatesEventAndIsReturnedByGetTitle() {
final String updatedTitle = "Updated Title";
try {
- mUiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- getActivity().setTitle(updatedTitle);
- }
- });
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED);
- }
- }, TIMEOUT_ASYNC_PROCESSING);
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mActivity.setTitle(updatedTitle)),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_TITLE),
+ TIMEOUT_ASYNC_PROCESSING);
} catch (TimeoutException exception) {
throw new RuntimeException(
"Failed to get windows changed event for title update", exception);
}
- AccessibilityWindowInfo window = findWindowByTitle(updatedTitle);
+ final AccessibilityWindowInfo window = findWindowByTitle(sUiAutomation, updatedTitle);
assertNotNull("Updated window title not reported to accessibility", window);
window.recycle();
}
+ @Test
+ public void testWindowAddedMovedAndRemoved_generatesEventsForAllThree() throws Exception {
+ final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
+ final WindowManager.LayoutParams paramsForBottom = layoutParmsForWindowOnBottom();
+ final Button button = new Button(mActivity);
+ button.setText(R.string.button1);
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mActivity.getWindowManager().addView(button, paramsForTop)),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED),
+ TIMEOUT_ASYNC_PROCESSING);
+
+ // Move window from top to bottom
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mActivity.getWindowManager().updateViewLayout(button, paramsForBottom)),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_BOUNDS),
+ TIMEOUT_ASYNC_PROCESSING);
+ // Remove the view
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mActivity.getWindowManager().removeView(button)),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED),
+ TIMEOUT_ASYNC_PROCESSING);
+ }
+
+ @Test
+ public void putWindowInPictureInPicture_generatesEventAndReportsProperty() throws Exception {
+ if (!sInstrumentation.getContext().getPackageManager()
+ .hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+ return;
+ }
+ sUiAutomation.executeAndWaitForEvent(
+ () -> sInstrumentation.runOnMainSync(() -> mActivity.enterPictureInPictureMode()),
+ (event) -> {
+ if (event.getEventType() != TYPE_WINDOWS_CHANGED) return false;
+ // Look for a picture-in-picture window
+ final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ if (windows.get(i).isInPictureInPictureMode()) {
+ return true;
+ }
+ }
+ return false;
+ }, TIMEOUT_ASYNC_PROCESSING);
+
+ // There should be exactly one picture-in-picture window now
+ int numPictureInPictureWindows = 0;
+ final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ final AccessibilityWindowInfo window = windows.get(i);
+ if (window.isInPictureInPictureMode()) {
+ numPictureInPictureWindows++;
+ }
+ }
+ assertTrue(numPictureInPictureWindows >= 1);
+ }
+
+ @Test
+ public void moveFocusToAnotherWindow_generatesEventsAndMovesActiveAndFocus() throws Exception {
+ final View topWindowView = showTopWindowAndWaitForItToShowUp();
+ final AccessibilityWindowInfo topWindow =
+ findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
+
+ AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+ final AccessibilityNodeInfo buttonNode =
+ topWindow.getRoot().findAccessibilityNodeInfosByText(
+ sInstrumentation.getContext().getString(R.string.button1)).get(0);
+
+ // Make sure activityWindow is not focused
+ if (activityWindow.isFocused()) {
+ sUiAutomation.executeAndWaitForEvent(
+ () -> buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED),
+ TIMEOUT_ASYNC_PROCESSING);
+ }
+
+ // Windows may have changed - refresh
+ activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+ assertFalse(activityWindow.isActive());
+ assertFalse(activityWindow.isFocused());
+
+ // Find a focusable view in the main activity menu
+ final AccessibilityNodeInfo autoCompleteTextInfo = activityWindow.getRoot()
+ .findAccessibilityNodeInfosByViewId(
+ "android.accessibilityservice.cts:id/autoCompleteLayout")
+ .get(0);
+
+ // Remove the top window and focus on the main activity
+ sUiAutomation.executeAndWaitForEvent(
+ () -> {
+ sInstrumentation.runOnMainSync(
+ () -> mActivity.getWindowManager().removeView(topWindowView));
+ buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ },
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED | WINDOWS_CHANGE_ACTIVE),
+ TIMEOUT_ASYNC_PROCESSING);
+ }
+
+ @Test
+ public void testChangeAccessibilityFocusWindow_getEvent() throws Exception {
+ final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+ info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+ sUiAutomation.setServiceInfo(info);
+
+ try {
+ showTopWindowAndWaitForItToShowUp();
+
+ final AccessibilityWindowInfo activityWindow =
+ findWindowByTitle(sUiAutomation, mActivityTitle);
+ final AccessibilityWindowInfo topWindow =
+ findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
+ final AccessibilityNodeInfo win2Node =
+ topWindow.getRoot().findAccessibilityNodeInfosByText(
+ sInstrumentation.getContext().getString(R.string.button1)).get(0);
+ final AccessibilityNodeInfo win1Node = activityWindow.getRoot()
+ .findAccessibilityNodeInfosByViewId(
+ "android.accessibilityservice.cts:id/autoCompleteLayout")
+ .get(0);
+
+ sUiAutomation.executeAndWaitForEvent(
+ () -> {
+ win2Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ win1Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ },
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
+ TIMEOUT_ASYNC_PROCESSING);
+ } finally {
+ info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+ sUiAutomation.setServiceInfo(info);
+ }
+ }
+
+ @Test
public void testGetAnchorForDropDownForAutoCompleteTextView_returnsTextViewNode() {
final AutoCompleteTextView autoCompleteTextView =
- (AutoCompleteTextView) getActivity().findViewById(R.id.autoCompleteLayout);
- AccessibilityNodeInfo autoCompleteTextInfo = mUiAutomation.getRootInActiveWindow()
+ (AutoCompleteTextView) mActivity.findViewById(R.id.autoCompleteLayout);
+ final AccessibilityNodeInfo autoCompleteTextInfo = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/autoCompleteLayout")
.get(0);
@@ -98,25 +271,15 @@
final String[] COUNTRIES = new String[] {"Belgium", "France", "Italy", "Germany", "Spain"};
try {
- mUiAutomation.executeAndWaitForEvent(new Runnable() {
- @Override
- public void run() {
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
- android.R.layout.simple_dropdown_item_1line, COUNTRIES);
- autoCompleteTextView.setAdapter(adapter);
- autoCompleteTextView.showDropDown();
- }
- });
- }
- }, new UiAutomation.AccessibilityEventFilter() {
- @Override
- public boolean accept(AccessibilityEvent event) {
- return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
- }
- }, TIMEOUT_ASYNC_PROCESSING);
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> {
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(
+ mActivity, android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+ autoCompleteTextView.setAdapter(adapter);
+ autoCompleteTextView.showDropDown();
+ }),
+ filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_CHILDREN),
+ TIMEOUT_ASYNC_PROCESSING);
} catch (TimeoutException exception) {
throw new RuntimeException(
"Failed to get window changed event when showing dropdown", exception);
@@ -124,9 +287,9 @@
// Find the pop-up window
boolean foundPopup = false;
- List<AccessibilityWindowInfo> windows = mUiAutomation.getWindows();
+ final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
for (int i = 0; i < windows.size(); i++) {
- AccessibilityWindowInfo window = windows.get(i);
+ final AccessibilityWindowInfo window = windows.get(i);
if (window.getAnchor() == null) {
continue;
}
@@ -137,17 +300,48 @@
assertTrue("Failed to find accessibility window for auto-complete pop-up", foundPopup);
}
- private AccessibilityWindowInfo findWindowByTitle(CharSequence title) {
- List<AccessibilityWindowInfo> windows = mUiAutomation.getWindows();
- AccessibilityWindowInfo returnValue = null;
- for (int i = 0; i < windows.size(); i++) {
- AccessibilityWindowInfo window = windows.get(i);
- if (TextUtils.equals(title, window.getTitle())) {
- returnValue = window;
- } else {
- window.recycle();
- }
- }
- return returnValue;
+ private View showTopWindowAndWaitForItToShowUp() throws TimeoutException {
+ final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
+ final Button button = new Button(mActivity);
+ button.setText(R.string.button1);
+ sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+ () -> mActivity.getWindowManager().addView(button, paramsForTop)),
+ (event) -> {
+ return (event.getEventType() == TYPE_WINDOWS_CHANGED)
+ && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
+ && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
+ },
+ TIMEOUT_ASYNC_PROCESSING);
+ return button;
}
-}
+
+ private WindowManager.LayoutParams layoutParmsForWindowOnTop() {
+ final WindowManager.LayoutParams params = layoutParmsForTestWindow();
+ params.gravity = Gravity.TOP;
+ params.setTitle(TOP_WINDOW_TITLE);
+ sInstrumentation.runOnMainSync(() -> {
+ params.y = getStatusBarHeight(mActivity);
+ });
+ return params;
+ }
+
+ private WindowManager.LayoutParams layoutParmsForWindowOnBottom() {
+ final WindowManager.LayoutParams params = layoutParmsForTestWindow();
+ params.gravity = Gravity.BOTTOM;
+ return params;
+ }
+
+ private WindowManager.LayoutParams layoutParmsForTestWindow() {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ sInstrumentation.runOnMainSync(() -> {
+ params.token = mActivity.getWindow().getDecorView().getWindowToken();
+ });
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
index 2b6d2dd..d6dc7fa 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/InstrumentedAccessibilityService.java
@@ -22,11 +22,15 @@
import static junit.framework.Assert.assertTrue;
public class InstrumentedAccessibilityService extends AccessibilityService {
+
+ private static final boolean DEBUG = false;
+
// Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
private static final String COMPONENT_NAME_SEPARATOR = ":";
- private static final int TIMEOUT_SERVICE_ENABLE = 10000;
- private static final int TIMEOUT_SERVICE_PERFORM_SYNC = 5000;
+ // Timeout disabled in #DEBUG mode to prevent breakpoint-related failures
+ private static final int TIMEOUT_SERVICE_ENABLE = DEBUG ? Integer.MAX_VALUE : 10000;
+ private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 5000;
private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
sInstances = new HashMap<>();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
new file mode 100644
index 0000000..5cc46ea1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.AsyncUtils.waitOn;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.distance;
+import static android.accessibilityservice.cts.utils.GestureUtils.drag;
+import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
+import static android.accessibilityservice.cts.utils.GestureUtils.lastPointOf;
+import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
+import static android.accessibilityservice.cts.utils.GestureUtils.pointerDown;
+import static android.accessibilityservice.cts.utils.GestureUtils.pointerUp;
+import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.graphics.PointF;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Class for testing magnification.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MagnificationGestureHandlerTest {
+
+ private static final double MIN_SCALE = 1.2;
+
+ private InstrumentedAccessibilityService mService;
+ private Instrumentation mInstrumentation;
+ private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener();
+ float mCurrentScale = 1f;
+ PointF mCurrentZoomCenter = null;
+ PointF mTapLocation;
+ PointF mTapLocation2;
+ private boolean mHasTouchscreen;
+ private boolean mOriginalIsMagnificationEnabled;
+
+ private final Object mZoomLock = new Object();
+
+ @Rule
+ public ActivityTestRule<GestureDispatchActivity> mActivityRule =
+ new ActivityTestRule<>(GestureDispatchActivity.class);
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ PackageManager pm = mInstrumentation.getContext().getPackageManager();
+ mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+ || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+ if (!mHasTouchscreen) return;
+
+ mOriginalIsMagnificationEnabled =
+ Settings.Secure.getInt(mInstrumentation.getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
+ setMagnificationEnabled(true);
+
+ mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
+ mService.getMagnificationController().addListener(
+ (controller, region, scale, centerX, centerY) -> {
+ mCurrentScale = scale;
+ mCurrentZoomCenter = isZoomed() ? new PointF(centerX, centerY) : null;
+
+ synchronized (mZoomLock) {
+ mZoomLock.notifyAll();
+ }
+ });
+
+ TextView view = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+ mInstrumentation.runOnMainSync(() -> {
+ view.setOnTouchListener(mTouchListener);
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+ mTapLocation = new PointF(xy[0] + view.getWidth() / 2, xy[1] + view.getHeight() / 2);
+ mTapLocation2 = add(mTapLocation, 31, 29);
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!mHasTouchscreen) return;
+
+ setMagnificationEnabled(mOriginalIsMagnificationEnabled);
+
+ if (mService != null) {
+ mService.runOnServiceSync(() -> mService.disableSelfAndRemove());
+ mService = null;
+ }
+ }
+
+ @Test
+ public void testZoomOnOff() {
+ if (!mHasTouchscreen) return;
+
+ assertFalse(isZoomed());
+
+ assertGesturesPropagateToView();
+ assertFalse(isZoomed());
+
+ setZoomByTripleTapping(true);
+
+ assertGesturesPropagateToView();
+ assertTrue(isZoomed());
+
+ setZoomByTripleTapping(false);
+ }
+
+ @Test
+ public void testViewportDragging() {
+ if (!mHasTouchscreen) return;
+
+ assertFalse(isZoomed());
+ tripleTapAndDragViewport();
+ waitOn(mZoomLock, () -> !isZoomed());
+
+ setZoomByTripleTapping(true);
+ tripleTapAndDragViewport();
+ assertTrue(isZoomed());
+
+ setZoomByTripleTapping(false);
+ }
+
+ @Test
+ public void testPanning() {
+ if (!mHasTouchscreen) return;
+ assertFalse(isZoomed());
+
+ float pan = Math.min(mTapLocation.x, mTapLocation2.x) / 2;
+
+ setZoomByTripleTapping(true);
+ PointF oldCenter = mCurrentZoomCenter;
+
+ dispatch(
+ swipe(mTapLocation, add(mTapLocation, -pan, 0)),
+ swipe(mTapLocation2, add(mTapLocation2, -pan, 0)));
+
+ waitOn(mZoomLock,
+ () -> (mCurrentZoomCenter.x - oldCenter.x >= pan / mCurrentScale * 0.9));
+
+ setZoomByTripleTapping(false);
+ }
+
+ private void setZoomByTripleTapping(boolean desiredZoomState) {
+ if (isZoomed() == desiredZoomState) return;
+ dispatch(tripleTap());
+ waitOn(mZoomLock, () -> isZoomed() == desiredZoomState);
+ assertNoTouchInputPropagated();
+ }
+
+ private void tripleTapAndDragViewport() {
+ StrokeDescription down = tripleTapAndHold();
+
+ float pan = mTapLocation.x / 2;
+ PointF oldCenter = mCurrentZoomCenter;
+
+ StrokeDescription drag = drag(down, add(lastPointOf(down), pan, 0f));
+ dispatch(drag);
+ waitOn(mZoomLock, () -> distance(mCurrentZoomCenter, oldCenter) >= pan / 5);
+ assertTrue(isZoomed());
+ assertNoTouchInputPropagated();
+
+ dispatch(pointerUp(drag));
+ assertNoTouchInputPropagated();
+ }
+
+ private StrokeDescription tripleTapAndHold() {
+ StrokeDescription tap1 = click(mTapLocation);
+ StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation2));
+ StrokeDescription down = startingAt(endTimeOf(tap2) + 20, pointerDown(mTapLocation));
+ dispatch(tap1, tap2, down);
+ waitOn(mZoomLock, () -> isZoomed());
+ return down;
+ }
+
+ private void assertGesturesPropagateToView() {
+ dispatch(click(mTapLocation));
+ assertPropagated(ACTION_DOWN, ACTION_UP);
+
+ dispatch(longClick(mTapLocation));
+ assertPropagated(ACTION_DOWN, ACTION_UP);
+
+ dispatch(doubleTap());
+ assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
+
+ dispatch(swipe(
+ mTapLocation,
+ add(mTapLocation, 31, 29)));
+ assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+ }
+
+ private void assertNoTouchInputPropagated() {
+ assertThat(prettyPrintable(mTouchListener.events), is(empty()));
+ }
+
+ private void setMagnificationEnabled(boolean enabled) {
+ Settings.Secure.putInt(mInstrumentation.getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, enabled ? 1 : 0);
+ }
+
+ private boolean isZoomed() {
+ return mCurrentScale >= MIN_SCALE;
+ }
+
+ private void assertPropagated(int... eventTypes) {
+ MotionEvent ev;
+ try {
+ while (true) {
+ if (eventTypes.length == 0) return;
+ int expectedEventType = eventTypes[0];
+ long startedPollingAt = SystemClock.uptimeMillis();
+ ev = mTouchListener.events.poll(5, SECONDS);
+ assertNotNull("Expected "
+ + MotionEvent.actionToString(expectedEventType)
+ + " but none present after "
+ + (SystemClock.uptimeMillis() - startedPollingAt) + "ms",
+ ev);
+ int action = ev.getActionMasked();
+ if (action == expectedEventType) {
+ eventTypes = Arrays.copyOfRange(eventTypes, 1, eventTypes.length);
+ } else {
+ if (action != ACTION_MOVE) fail("Unexpected event: " + ev);
+ }
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private GestureDescription doubleTap() {
+ return multiTap(2);
+ }
+
+ private GestureDescription tripleTap() {
+ return multiTap(3);
+ }
+
+ private GestureDescription multiTap(int taps) {
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ long time = 0;
+ for (int i = 0; i < taps; i++) {
+ StrokeDescription stroke = click(mTapLocation);
+ builder.addStroke(startingAt(time, stroke));
+ time += stroke.getDuration() + 20;
+ }
+ return builder.build();
+ }
+
+ public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+ GestureDescription.Builder builder =
+ new GestureDescription.Builder().addStroke(firstStroke);
+ for (StrokeDescription stroke : rest) {
+ builder.addStroke(stroke);
+ }
+ dispatch(builder.build());
+ }
+
+ public void dispatch(GestureDescription gesture) {
+ await(dispatchGesture(mService, gesture));
+ }
+
+ private static <T> Collection<T> prettyPrintable(Collection<T> c) {
+ return new ArrayList<T>(c) {
+
+ @Override
+ public String toString() {
+ return stream()
+ .map(t -> "\n" + t)
+ .reduce(String::concat)
+ .orElse("");
+ }
+ };
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
index 823b8d6..6bad735 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/ShellCommandBuilder.java
@@ -69,7 +69,7 @@
return this;
}
- private static void execShellCommand(UiAutomation automation, String command) {
+ public static void execShellCommand(UiAutomation automation, String command) {
try (ParcelFileDescriptor fd = automation.executeShellCommand(command)) {
try (InputStream inputStream = new FileInputStream(fd.getFileDescriptor())) {
try (BufferedReader reader = new BufferedReader(
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
new file mode 100644
index 0000000..cd9055d
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityEndToEndActivity.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 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.accessibilityservice.cts.activities;
+
+import android.accessibilityservice.cts.R;
+
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * This class is an {@link android.app.Activity} used to perform end-to-end
+ * testing of the accessibility feature by interaction with the
+ * UI widgets.
+ */
+public class AccessibilityEndToEndActivity extends AccessibilityTestActivity {
+ private PackageNameInjector mPackageNameInjector;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.end_to_end_test);
+
+ ListAdapter listAdapter = new BaseAdapter() {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView textView = (TextView) View
+ .inflate(AccessibilityEndToEndActivity.this, R.layout.list_view_row, null);
+ textView.setText((String) getItem(position));
+ return textView;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public Object getItem(int position) {
+ if (position == 0) {
+ return AccessibilityEndToEndActivity.this.getString(R.string.first_list_item);
+ } else {
+ return AccessibilityEndToEndActivity.this.getString(R.string.second_list_item);
+ }
+ }
+
+ public int getCount() {
+ return 2;
+ }
+ };
+
+ ListView listView = (ListView) findViewById(R.id.listview);
+ listView.setAdapter(listAdapter);
+ }
+
+ public void setReportedPackageName(String packageName) {
+ if (packageName != null) {
+ mPackageNameInjector = new PackageNameInjector(packageName);
+ } else {
+ mPackageNameInjector = null;
+ }
+ setPackageNameInjector(getWindow().getDecorView(), mPackageNameInjector);
+ }
+
+ private static void setPackageNameInjector(View node, PackageNameInjector injector) {
+ if (node == null) {
+ return;
+ }
+ node.setAccessibilityDelegate(injector);
+ if (node instanceof ViewGroup) {
+ final ViewGroup viewGroup = (ViewGroup) node;
+ final int childCount = viewGroup.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ setPackageNameInjector(viewGroup.getChildAt(i), injector);
+ }
+ }
+ }
+
+ private static class PackageNameInjector extends View.AccessibilityDelegate {
+ private final String mPackageName;
+
+ PackageNameInjector(String packageName) {
+ mPackageName = packageName;
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ event.setPackageName(mPackageName);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setPackageName(mPackageName);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityFocusAndInputFocusSyncActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityFocusAndInputFocusSyncActivity.java
new file mode 100644
index 0000000..921f01b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityFocusAndInputFocusSyncActivity.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2012 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.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity for testing the accessibility focus APIs exposed to
+ * accessibility services. These APIs allow moving accessibility
+ * focus in the view tree from an AccessiiblityService. Specifically,
+ * this activity is for verifying the the sync between accessibility
+ * and input focus.
+ */
+public class AccessibilityFocusAndInputFocusSyncActivity extends AccessibilityTestActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_focus_and_input_focus_sync_test);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
new file mode 100644
index 0000000..49be337
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTestActivity.java
@@ -0,0 +1,31 @@
+/**
+ * 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.accessibilityservice.cts.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public abstract class AccessibilityTestActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTextTraversalActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTextTraversalActivity.java
new file mode 100644
index 0000000..2cd28c5
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityTextTraversalActivity.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2012 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.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity for testing the accessibility APIs for traversing the
+ * text content of a View at several granularities.
+ */
+public class AccessibilityTextTraversalActivity extends AccessibilityTestActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_text_traversal_test);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityViewTreeReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityViewTreeReportingActivity.java
new file mode 100644
index 0000000..28fada1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityViewTreeReportingActivity.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2012 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.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity for testing the accessibility focus APIs exposed to
+ * accessibility services. These APIs allow moving accessibility
+ * focus in the view tree from an AccessiiblityService. Specifically,
+ * this activity is for verifying the hierarchical movement of the
+ * accessibility focus.
+ */
+public class AccessibilityViewTreeReportingActivity extends AccessibilityTestActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_view_tree_reporting_test);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowQueryActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowQueryActivity.java
new file mode 100644
index 0000000..4424f58
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowQueryActivity.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+import android.view.View;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.accessibilityservice.cts.R;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessibilityService.
+ */
+public class AccessibilityWindowQueryActivity extends AccessibilityTestActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.query_window_test);
+
+ findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ /* do nothing */
+ }
+ });
+ findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ return true;
+ }
+ });
+
+ findViewById(R.id.button5).setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(new AccessibilityAction(R.id.foo_custom_action, "Foo"));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == R.id.foo_custom_action) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowReportingActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowReportingActivity.java
new file mode 100644
index 0000000..dfbbfa3
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityWindowReportingActivity.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.activities;
+
+import android.os.Bundle;
+
+/**
+ * Activity used by ActivityWindowReportingTest
+ */
+public class AccessibilityWindowReportingActivity extends AccessibilityTestActivity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(
+ android.accessibilityservice.cts.R.layout.accessibility_window_reporting_test);
+ setTitle("AccessibilityWindowReportingActivity");
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
new file mode 100644
index 0000000..846acd9
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+import static org.hamcrest.CoreMatchers.both;
+
+import android.app.UiAutomation.AccessibilityEventFilter;
+import android.view.accessibility.AccessibilityEvent;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Utility class for creating AccessibilityEventFilters
+ */
+public class AccessibilityEventFilterUtils {
+ public static AccessibilityEventFilter filterForEventType(int eventType) {
+ return (new AccessibilityEventTypeMatcher(eventType))::matches;
+ }
+
+ public static AccessibilityEventFilter filterWindowsChangedWithChangeTypes(int changes) {
+ return (both(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED))
+ .and(new WindowChangesMatcher(changes)))::matches;
+ }
+ public static class AccessibilityEventTypeMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private int mType;
+
+ public AccessibilityEventTypeMatcher(int type) {
+ super();
+ mType = type;
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return event.getEventType() == mType;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to type " + mType);
+ }
+ }
+
+ public static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private int mWindowChanges;
+
+ public WindowChangesMatcher(int windowChanges) {
+ super();
+ mWindowChanges = windowChanges;
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return (event.getWindowChanges() & mWindowChanges) == mWindowChanges;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("With window change type " + mWindowChanges);
+ }
+ }
+
+ public static class ContentChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private int mContentChanges;
+
+ public ContentChangesMatcher(int contentChanges) {
+ super();
+ mContentChanges = contentChanges;
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return (event.getContentChangeTypes() & mContentChanges) == mContentChanges;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("With window change type " + mContentChanges);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
new file mode 100644
index 0000000..312bdf1
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+import static android.accessibilityservice.cts.AccessibilityActivityTestCase
+ .TIMEOUT_ASYNC_PROCESSING;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.graphics.Rect;
+import android.support.test.rule.ActivityTestRule;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import java.util.List;
+
+/**
+ * Utilities useful when launching an activity to make sure it's all the way on the screen
+ * before we start testing it.
+ */
+public class ActivityLaunchUtils {
+ // Using a static variable so it can be used in lambdas. Not preserving state in it.
+ private static Activity mTempActivity;
+
+ public static Activity launchActivityAndWaitForItToBeOnscreen(Instrumentation instrumentation,
+ UiAutomation uiAutomation, ActivityTestRule<? extends Activity> rule) throws Exception {
+ final int[] location = new int[2];
+ final StringBuilder activityPackage = new StringBuilder();
+ final Rect bounds = new Rect();
+ final StringBuilder activityTitle = new StringBuilder();
+ final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
+ () -> {
+ mTempActivity = rule.launchActivity(null);
+ final StringBuilder builder = new StringBuilder();
+ instrumentation.runOnMainSync(() -> {
+ mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+ activityPackage.append(mTempActivity.getPackageName());
+ });
+ instrumentation.waitForIdleSync();
+ activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
+ },
+ (event) -> {
+ final AccessibilityWindowInfo window =
+ findWindowByTitle(uiAutomation, activityTitle);
+ if (window == null) return false;
+ window.getBoundsInScreen(bounds);
+ mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+ if (bounds.isEmpty()) {
+ return false;
+ }
+ return (!bounds.isEmpty())
+ && (bounds.left == location[0]) && (bounds.top == location[1]);
+ }, TIMEOUT_ASYNC_PROCESSING);
+ assertNotNull(awaitedEvent);
+ return mTempActivity;
+ }
+
+ public static CharSequence getActivityTitle(
+ Instrumentation instrumentation, Activity activity) {
+ final StringBuilder titleBuilder = new StringBuilder();
+ instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle()));
+ return titleBuilder;
+ }
+
+ public static AccessibilityWindowInfo findWindowByTitle(
+ UiAutomation uiAutomation, CharSequence title) {
+ final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
+ AccessibilityWindowInfo returnValue = null;
+ for (int i = 0; i < windows.size(); i++) {
+ final AccessibilityWindowInfo window = windows.get(i);
+ if (TextUtils.equals(title, window.getTitle())) {
+ returnValue = window;
+ } else {
+ window.recycle();
+ }
+ }
+ return returnValue;
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
new file mode 100644
index 0000000..12bb737
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AsyncUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+import static android.accessibilityservice.cts.utils.CtsTestUtils.assertThrows;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.SystemClock;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+
+public class AsyncUtils {
+ public static final long DEFAULT_TIMEOUT_MS = 5000;
+
+ private AsyncUtils() {}
+
+ public static <T> T await(Future<T> f) {
+ return await(f, DEFAULT_TIMEOUT_MS, MILLISECONDS);
+ }
+
+ public static <T> T await(Future<T> f, long time, TimeUnit timeUnit) {
+ try {
+ return f.get(time, timeUnit);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Throwable awaitFailure(Future<?> f) {
+ return awaitFailure(f, DEFAULT_TIMEOUT_MS, MILLISECONDS);
+ }
+
+ public static Throwable awaitFailure(Future<?> f, long time, TimeUnit timeUnit) {
+ return assertThrows(() -> await(f, time, timeUnit));
+ }
+
+ public static <T> CancellationException awaitCancellation(Future<T> f) {
+ return awaitCancellation(f, DEFAULT_TIMEOUT_MS, MILLISECONDS);
+ }
+
+ public static <T> CancellationException awaitCancellation(
+ Future<T> f, long time, TimeUnit timeUnit) {
+ Throwable ex = awaitFailure(f, time, timeUnit);
+ Throwable current = ex;
+ while (current != null) {
+ if (current instanceof CancellationException) {
+ return (CancellationException) current;
+ }
+ current = current.getCause();
+ }
+ throw new AssertionError("Expected cancellation", ex);
+ }
+
+ public static void waitOn(Object notifyLock, BooleanSupplier condition) {
+ waitOn(notifyLock, condition, DEFAULT_TIMEOUT_MS);
+ }
+
+ public static void waitOn(Object notifyLock, BooleanSupplier condition, long timeoutMs) {
+ if (condition.getAsBoolean()) return;
+
+ synchronized (notifyLock) {
+ try {
+ long timeSlept = 0;
+ while (!condition.getAsBoolean() && timeSlept < timeoutMs) {
+ long sleepStart = SystemClock.uptimeMillis();
+ notifyLock.wait(timeoutMs - timeSlept);
+ timeSlept += SystemClock.uptimeMillis() - sleepStart;
+ }
+ if (!condition.getAsBoolean()) {
+ throw new AssertionError("Timed out after " + timeSlept
+ + "ms waiting for condition");
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/CtsTestUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/CtsTestUtils.java
new file mode 100644
index 0000000..545483b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/CtsTestUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+
+public class CtsTestUtils {
+ private CtsTestUtils() {}
+
+ public static Throwable assertThrows(Runnable action) {
+ return assertThrows(Throwable.class, action);
+ }
+
+ public static <E extends Throwable> E assertThrows(Class<E> exceptionClass, Runnable action) {
+ try {
+ action.run();
+ throw new AssertionError("Expected an exception");
+ } catch (Throwable e) {
+ if (exceptionClass.isInstance(e)) {
+ return (E) e;
+ }
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
new file mode 100644
index 0000000..a65d859
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.view.Window;
+
+/**
+ * Utilities needed when interacting with the display
+ */
+public class DisplayUtils {
+ public static int getStatusBarHeight(Activity activity) {
+ final Rect rect = new Rect();
+ Window window = activity.getWindow();
+ window.getDecorView().getWindowVisibleDisplayFrame(rect);
+ return rect.top;
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
new file mode 100644
index 0000000..d7ae4592
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+
+import static org.junit.Assert.assertTrue;
+
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class EventCapturingTouchListener implements View.OnTouchListener {
+
+ public final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ assertTrue(events.offer(MotionEvent.obtain(motionEvent)));
+ return true;
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
new file mode 100644
index 0000000..076a6da
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts.utils;
+
+import android.accessibilityservice.AccessibilityService.GestureResultCallback;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.InstrumentedAccessibilityService;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.view.ViewConfiguration;
+
+import java.util.concurrent.CompletableFuture;
+
+public class GestureUtils {
+
+ private GestureUtils() {}
+
+ public static CompletableFuture<Void> dispatchGesture(
+ InstrumentedAccessibilityService service,
+ GestureDescription gesture) {
+ CompletableFuture<Void> result = new CompletableFuture<>();
+ GestureResultCallback callback = new GestureResultCallback() {
+ @Override
+ public void onCompleted(GestureDescription gestureDescription) {
+ result.complete(null);
+ }
+
+ @Override
+ public void onCancelled(GestureDescription gestureDescription) {
+ result.cancel(false);
+ }
+ };
+ service.runOnServiceSync(() -> {
+ if (!service.dispatchGesture(gesture, callback, null)) {
+ result.completeExceptionally(new IllegalStateException());
+ }
+ });
+ return result;
+ }
+
+ public static StrokeDescription pointerDown(PointF point) {
+ return new StrokeDescription(path(point), 0, ViewConfiguration.getTapTimeout(), true);
+ }
+
+ public static StrokeDescription pointerUp(StrokeDescription lastStroke) {
+ return lastStroke.continueStroke(path(lastPointOf(lastStroke)),
+ endTimeOf(lastStroke), ViewConfiguration.getTapTimeout(), false);
+ }
+
+ public static PointF lastPointOf(StrokeDescription stroke) {
+ float[] p = stroke.getPath().approximate(0.3f);
+ return new PointF(p[p.length - 2], p[p.length - 1]);
+ }
+
+ public static StrokeDescription click(PointF point) {
+ return new StrokeDescription(path(point), 0, ViewConfiguration.getTapTimeout());
+ }
+
+ public static StrokeDescription longClick(PointF point) {
+ return new StrokeDescription(path(point), 0,
+ ViewConfiguration.getLongPressTimeout() * 3 / 2);
+ }
+
+ public static StrokeDescription swipe(PointF from, PointF to) {
+ return swipe(from, to, ViewConfiguration.getTapTimeout());
+ }
+
+ public static StrokeDescription swipe(PointF from, PointF to, long duration) {
+ return new StrokeDescription(path(from, to), 0, duration);
+ }
+
+ public static StrokeDescription drag(StrokeDescription from, PointF to) {
+ return from.continueStroke(
+ path(lastPointOf(from), to),
+ endTimeOf(from), ViewConfiguration.getTapTimeout(), true);
+ }
+
+ public static Path path(PointF first, PointF... rest) {
+ Path path = new Path();
+ path.moveTo(first.x, first.y);
+ for (PointF point : rest) {
+ path.lineTo(point.x, point.y);
+ }
+ return path;
+ }
+
+ public static StrokeDescription startingAt(long timeMs, StrokeDescription prototype) {
+ return new StrokeDescription(
+ prototype.getPath(), timeMs, prototype.getDuration(), prototype.willContinue());
+ }
+
+ public static long endTimeOf(StrokeDescription stroke) {
+ return stroke.getStartTime() + stroke.getDuration();
+ }
+
+ public static float distance(PointF a, PointF b) {
+ if (a == null) throw new NullPointerException();
+ if (b == null) throw new NullPointerException();
+ return (float) Math.hypot(a.x - b.x, a.y - b.y);
+ }
+
+ public static PointF add(PointF a, float x, float y) {
+ return new PointF(a.x + x, a.y + y);
+ }
+
+ public static PointF add(PointF a, PointF b) {
+ return add(a, b.x, b.y);
+ }
+
+ public static PointF diff(PointF a, PointF b) {
+ return add(a, -b.x, -b.y);
+ }
+
+ public static PointF negate(PointF p) {
+ return times(-1, p);
+ }
+
+ public static PointF times(float mult, PointF p) {
+ return new PointF(p.x * mult, p.y * mult);
+ }
+
+ public static float length(PointF p) {
+ return (float) Math.hypot(p.x, p.y);
+ }
+
+ public static PointF ceil(PointF p) {
+ return new PointF((float) Math.ceil(p.x), (float) Math.ceil(p.y));
+ }
+}
diff --git a/tests/accessibilityservice/test-apps/Android.mk b/tests/accessibilityservice/test-apps/Android.mk
new file mode 100644
index 0000000..c9afcf1
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/Android.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/Android.mk b/tests/accessibilityservice/test-apps/WidgetProvider/Android.mk
new file mode 100644
index 0000000..a0c1d97
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsAccessibilityWidgetProvider
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
new file mode 100644
index 0000000..4a06f03
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foo.bar.baz">
+
+ <application>
+ <receiver android:name="foo.bar.baz.MyAppWidgetProvider" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_info" />
+ </receiver>
+ </application>
+
+</manifest>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/drawable/android_icon.png b/tests/accessibilityservice/test-apps/WidgetProvider/res/drawable/android_icon.png
new file mode 100644
index 0000000..4ba97a5
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/drawable/android_icon.png
Binary files differ
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/layout/initial_layout.xml b/tests/accessibilityservice/test-apps/WidgetProvider/res/layout/initial_layout.xml
new file mode 100644
index 0000000..bebec9f
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/layout/initial_layout.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+</Button>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/values/constants.xml b/tests/accessibilityservice/test-apps/WidgetProvider/res/values/constants.xml
new file mode 100644
index 0000000..b29e8da
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/values/constants.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- App widget provider -->
+ <dimen name="min_appwidget_size">40dp</dimen>
+ <dimen name="min_resize_appwidget_size">60dp</dimen>
+ <integer name="update_period_millis">86400000</integer>
+ <integer name="resize_mode">3</integer>
+ <integer name="widget_category">3</integer>
+ <item type="id" name="auto_advance_view_id"/>
+</resources>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/res/xml/appwidget_info.xml b/tests/accessibilityservice/test-apps/WidgetProvider/res/xml/appwidget_info.xml
new file mode 100644
index 0000000..283f543
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/res/xml/appwidget_info.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="@dimen/min_appwidget_size"
+ android:minHeight="@dimen/min_appwidget_size"
+ android:minResizeWidth="@dimen/min_resize_appwidget_size"
+ android:minResizeHeight="@dimen/min_resize_appwidget_size"
+ android:updatePeriodMillis="@integer/update_period_millis"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen"
+ android:initialLayout="@layout/initial_layout"
+ android:previewImage="@drawable/android_icon"
+ android:autoAdvanceViewId="@id/auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/src/foo/bar/baz/MyAppWidgetProvider.java b/tests/accessibilityservice/test-apps/WidgetProvider/src/foo/bar/baz/MyAppWidgetProvider.java
new file mode 100644
index 0000000..e30b554
--- /dev/null
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/src/foo/bar/baz/MyAppWidgetProvider.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package foo.bar.baz;
+
+import android.appwidget.AppWidgetProvider;
+
+public class MyAppWidgetProvider extends AppWidgetProvider {}
diff --git a/tests/admin/Android.mk b/tests/admin/Android.mk
index 24fdda3..bd5346d 100644
--- a/tests/admin/Android.mk
+++ b/tests/admin/Android.mk
@@ -23,6 +23,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner mockito-target-minus-junit4
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAdminTestCases
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index a0233d7..02c5e3f 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for the CTS device admin tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/admin/app/Android.mk b/tests/admin/app/Android.mk
index 22b5c83..947fb7d 100644
--- a/tests/admin/app/Android.mk
+++ b/tests/admin/app/Android.mk
@@ -22,7 +22,7 @@
LOCAL_JAVA_LIBRARIES := guava
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 4b9e5fa..938939f 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -746,8 +746,7 @@
}
private void assertProfileOwnerMessage(String message) {
- assertTrue("message is: "+ message,
- message.contains("does not own the profile"));
+ assertTrue("message is: "+ message, message.contains("does not own the profile"));
}
public void testSetDelegatedCertInstaller_failIfNotProfileOwner() {
@@ -901,4 +900,43 @@
assertProfileOwnerMessage(e.getMessage());
}
}
+
+ public void testIsUsingUnifiedPassword_failIfNotProfileOwner() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testIsUsingUnifiedPassword_failIfNotProfileOwner");
+ return;
+ }
+ try {
+ mDevicePolicyManager.isUsingUnifiedPassword(mComponent);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException e) {
+ assertProfileOwnerMessage(e.getMessage());
+ }
+ }
+
+ public void testSetPasswordBlacklist_failIfNotDeviceOrProfileOwner() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testSetPasswordBlacklist_failIfNotDeviceOrProfileOwner");
+ return;
+ }
+ try {
+ mDevicePolicyManager.setPasswordBlacklist(mComponent, null, null);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException e) {
+ assertProfileOwnerMessage(e.getMessage());
+ }
+ }
+
+ public void testGetPasswordBlacklistName_failIfNotDeviceOrProfileOwner() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testGetPasswordBlacklistName_failIfNotDeviceOrProfileOwner");
+ return;
+ }
+ try {
+ mDevicePolicyManager.getPasswordBlacklistName(mComponent);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException e) {
+ assertProfileOwnerMessage(e.getMessage());
+ }
+ }
}
diff --git a/tests/app/Android.mk b/tests/app/Android.mk
index 6bd42ef..66d5de0 100644
--- a/tests/app/Android.mk
+++ b/tests/app/Android.mk
@@ -21,7 +21,13 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ telephony-common \
+ voip-common \
+ org.apache.http.legacy \
+ android.test.base.stubs \
+
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
@@ -29,12 +35,11 @@
ctstestserver \
mockito-target-minus-junit4 \
android-support-test \
- platform-test-annotations
+ platform-test-annotations \
+ cts-amwm-util
LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, app2/src) \
- $(call all-java-files-under, appSdk25/src) \
+ $(call all-java-files-under, src)
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index eb515b2..7aa3e20 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS App test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -22,7 +23,8 @@
<option name="test-file-name" value="CtsAppTestStubs.apk" />
<option name="test-file-name" value="CtsAppTestStubsDifferentUid.apk" />
<option name="test-file-name" value="CtsAppTestCases.apk" />
- <option name="test-file-name" value="CtsAppTestSdk25.apk" />
+ <option name="test-file-name" value="CtsCantSaveState1.apk" />
+ <option name="test-file-name" value="CtsCantSaveState2.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.app.cts" />
diff --git a/tests/app/CantSaveState1/Android.mk b/tests/app/CantSaveState1/Android.mk
new file mode 100644
index 0000000..ad7e880
--- /dev/null
+++ b/tests/app/CantSaveState1/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsCantSaveState1
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/CantSaveState1/AndroidManifest.xml b/tests/app/CantSaveState1/AndroidManifest.xml
new file mode 100644
index 0000000..fadcaeb
--- /dev/null
+++ b/tests/app/CantSaveState1/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.cantsavestate1">
+ <application android:label="Can't Save 1" android:cantSaveState="true">
+ <activity android:name="CantSave1Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/app/CantSaveState1/res/layout/cant_save_1_activity.xml b/tests/app/CantSaveState1/res/layout/cant_save_1_activity.xml
new file mode 100644
index 0000000..c5bf657
--- /dev/null
+++ b/tests/app/CantSaveState1/res/layout/cant_save_1_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="This app #1 can't save its state"
+ />
+
+</LinearLayout>
diff --git a/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
new file mode 100644
index 0000000..fb678cb
--- /dev/null
+++ b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.cantsavestate1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CantSave1Activity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.cant_save_1_activity);
+ getWindow().getDecorView().requestFocus();
+ }
+}
diff --git a/tests/app/CantSaveState2/Android.mk b/tests/app/CantSaveState2/Android.mk
new file mode 100644
index 0000000..05da3f6
--- /dev/null
+++ b/tests/app/CantSaveState2/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsCantSaveState2
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/CantSaveState2/AndroidManifest.xml b/tests/app/CantSaveState2/AndroidManifest.xml
new file mode 100644
index 0000000..8f4f01d
--- /dev/null
+++ b/tests/app/CantSaveState2/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.cantsavestate2">
+ <application android:label="Can't Save 2" android:cantSaveState="true">
+ <activity android:name="CantSave2Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/app/CantSaveState2/res/layout/cant_save_2_activity.xml b/tests/app/CantSaveState2/res/layout/cant_save_2_activity.xml
new file mode 100644
index 0000000..c5b8e3d
--- /dev/null
+++ b/tests/app/CantSaveState2/res/layout/cant_save_2_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="This app #2 can't save its state"
+ />
+
+</LinearLayout>
diff --git a/tests/app/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java b/tests/app/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java
new file mode 100644
index 0000000..420ac93
--- /dev/null
+++ b/tests/app/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.cantsavestate2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CantSave2Activity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.cant_save_2_activity);
+ getWindow().getDecorView().requestFocus();
+ }
+}
diff --git a/tests/app/app/Android.mk b/tests/app/app/Android.mk
index 191f4cc..d982246 100644
--- a/tests/app/app/Android.mk
+++ b/tests/app/app/Android.mk
@@ -23,15 +23,20 @@
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ telephony-common \
+ voip-common \
+ org.apache.http.legacy \
+ android.test.base \
+
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
ctstestrunner \
ctstestserver \
mockito-target-minus-junit4 \
- android-support-v4 \
- legacy-android-test
+ android-support-v4
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
src/android/app/stubs/ISecondary.aidl
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 7db0717..6093384 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -104,6 +104,8 @@
<service android:name="android.app.stubs.MockService" />
+ <service android:name="android.app.stubs.NullService" />
+
<activity android:name="android.app.stubs.SearchManagerStubActivity"
android:label="SearchManagerStubActivity">
<intent-filter>
@@ -326,45 +328,6 @@
<activity android:name="android.app.stubs.ToolbarActivity"
android:theme="@android:style/Theme.Material.Light.NoActionBar" />
- <activity android:name="android.app.stubs.MaxAspectRatioActivity"
- android:label="MaxAspectRatioActivity"
- android:resizeableActivity="false"
- android:maxAspectRatio="1.0">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
- <activity android:name="android.app.stubs.MetaDataMaxAspectRatioActivity"
- android:label="MetaDataMaxAspectRatioActivity"
- android:resizeableActivity="false">
- <meta-data android:name="android.max_aspect" android:value="1.0" />
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
- <activity android:name="android.app.stubs.MaxAspectRatioResizeableActivity"
- android:label="MaxAspectRatioResizeableActivity"
- android:resizeableActivity="true"
- android:maxAspectRatio="1.0">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
- <activity android:name="android.app.stubs.MaxAspectRatioUnsetActivity"
- android:label="MaxAspectRatioUnsetActivity"
- android:resizeableActivity="false">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<service
android:name="android.app.stubs.LiveWallpaper"
android:icon="@drawable/robot"
diff --git a/tests/app/app/src/android/app/stubs/MaxAspectRatioActivity.java b/tests/app/app/src/android/app/stubs/MaxAspectRatioActivity.java
deleted file mode 100644
index 4a04861..0000000
--- a/tests/app/app/src/android/app/stubs/MaxAspectRatioActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.stubs;
-
-import android.app.Activity;
-
-public class MaxAspectRatioActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/MaxAspectRatioResizeableActivity.java b/tests/app/app/src/android/app/stubs/MaxAspectRatioResizeableActivity.java
deleted file mode 100644
index da1c605..0000000
--- a/tests/app/app/src/android/app/stubs/MaxAspectRatioResizeableActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.stubs;
-
-import android.app.Activity;
-
-public class MaxAspectRatioResizeableActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/MaxAspectRatioUnsetActivity.java b/tests/app/app/src/android/app/stubs/MaxAspectRatioUnsetActivity.java
deleted file mode 100644
index 06e4994..0000000
--- a/tests/app/app/src/android/app/stubs/MaxAspectRatioUnsetActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.stubs;
-
-import android.app.Activity;
-
-public class MaxAspectRatioUnsetActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/MetaDataMaxAspectRatioActivity.java b/tests/app/app/src/android/app/stubs/MetaDataMaxAspectRatioActivity.java
deleted file mode 100644
index 2e989de..0000000
--- a/tests/app/app/src/android/app/stubs/MetaDataMaxAspectRatioActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.stubs;
-
-import android.app.Activity;
-
-public class MetaDataMaxAspectRatioActivity extends Activity {
-
-}
diff --git a/tests/app/app/src/android/app/stubs/NullService.java b/tests/app/app/src/android/app/stubs/NullService.java
new file mode 100644
index 0000000..6a1c618
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/NullService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.stubs;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * A Service that always returns null from onBind(Intent).
+ */
+public class NullService extends Service {
+ private static final String TAG = "NullService";
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind() returning null");
+ return null;
+ }
+
+}
diff --git a/tests/app/app2/Android.mk b/tests/app/app2/Android.mk
index 2689c30..304f79e 100644
--- a/tests/app/app2/Android.mk
+++ b/tests/app/app2/Android.mk
@@ -22,8 +22,7 @@
compatibility-device-util \
LOCAL_SRC_FILES := \
- ../app/src/android/app/stubs/LocalService.java \
- $(call all-java-files-under, src) \
+ ../app/src/android/app/stubs/LocalService.java
LOCAL_SDK_VERSION := current
diff --git a/tests/app/app2/AndroidManifest.xml b/tests/app/app2/AndroidManifest.xml
index 8c30996..0926251 100644
--- a/tests/app/app2/AndroidManifest.xml
+++ b/tests/app/app2/AndroidManifest.xml
@@ -19,6 +19,8 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<service android:name="android.app.stubs.LocalService"
android:exported="true"/>
<service android:name=".AlertWindowService"
diff --git a/tests/app/app2/src/com/android/app2/AlertWindowService.java b/tests/app/app2/src/com/android/app2/AlertWindowService.java
deleted file mode 100644
index a514e8a..0000000
--- a/tests/app/app2/src/com/android/app2/AlertWindowService.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.app2;
-
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Point;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import java.util.LinkedList;
-
-import static android.graphics.Color.BLUE;
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.TOP;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-/** Service for creating and managing alert windows. */
-public class AlertWindowService extends Service {
-
- private static final String TAG = "AlertWindowService";
- private static final boolean DEBUG = false;
-
- public static final int MSG_ADD_ALERT_WINDOW = 1;
- public static final int MSG_REMOVE_ALERT_WINDOW = 2;
- public static final int MSG_REMOVE_ALL_ALERT_WINDOWS = 3;
-
- public static String NOTIFICATION_MESSENGER_EXTRA =
- "com.android.app2.AlertWindowService.NOTIFICATION_MESSENGER_EXTRA";
- public static final int MSG_ON_ALERT_WINDOW_ADDED = 4;
- public static final int MSG_ON_ALERT_WINDOW_REMOVED = 5;
-
- private LinkedList<View> mAlertWindows = new LinkedList<>();
-
- private Messenger mOutgoingMessenger = null;
- private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
-
- private class IncomingHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ADD_ALERT_WINDOW:
- addAlertWindow();
- break;
- case MSG_REMOVE_ALERT_WINDOW:
- removeAlertWindow();
- break;
- case MSG_REMOVE_ALL_ALERT_WINDOWS:
- removeAllAlertWindows();
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
-
- private void addAlertWindow() {
- final Point size = new Point();
- final WindowManager wm = getSystemService(WindowManager.class);
- wm.getDefaultDisplay().getSize(size);
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
- params.width = size.x / 3;
- params.height = size.y / 3;
- params.gravity = TOP | LEFT;
-
- final TextView view = new TextView(this);
- view.setText("AlertWindowService" + mAlertWindows.size());
- view.setBackgroundColor(BLUE);
- wm.addView(view, params);
- mAlertWindows.add(view);
-
- if (DEBUG) Log.e(TAG, "addAlertWindow " + mAlertWindows.size());
- if (mOutgoingMessenger != null) {
- try {
- mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_ADDED));
- } catch (RemoteException e) {
-
- }
- }
- }
-
- private void removeAlertWindow() {
- if (mAlertWindows.size() == 0) {
- return;
- }
- final WindowManager wm = getSystemService(WindowManager.class);
- wm.removeView(mAlertWindows.pop());
-
- if (DEBUG) Log.e(TAG, "removeAlertWindow " + mAlertWindows.size());
- if (mOutgoingMessenger != null) {
- try {
- mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_REMOVED));
- } catch (RemoteException e) {
-
- }
- }
- }
-
- private void removeAllAlertWindows() {
- while (mAlertWindows.size() > 0) {
- removeAlertWindow();
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- if (DEBUG) Log.e(TAG, "onBind");
- mOutgoingMessenger = intent.getParcelableExtra(NOTIFICATION_MESSENGER_EXTRA);
- return mIncomingMessenger.getBinder();
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- if (DEBUG) Log.e(TAG, "onUnbind");
- removeAllAlertWindows();
- return super.onUnbind(intent);
- }
-}
diff --git a/tests/app/appSdk25/Android.mk b/tests/app/appSdk25/Android.mk
deleted file mode 100644
index 36c6d07..0000000
--- a/tests/app/appSdk25/Android.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- compatibility-device-util \
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := 25
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsAppTestSdk25
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/app/appSdk25/AndroidManifest.xml b/tests/app/appSdk25/AndroidManifest.xml
deleted file mode 100755
index f564f51..0000000
--- a/tests/app/appSdk25/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.appSdk25">
-
- <application android:label="CtsAppTestSdk25">
- <activity android:name=".Sdk25MaxAspectRatioActivity"
- android:label="Sdk25MaxAspectRatioActivity"
- android:resizeableActivity="false"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
- </application>
-</manifest>
-
diff --git a/tests/app/appSdk25/src/com/android/appSdk25/Sdk25MaxAspectRatioActivity.java b/tests/app/appSdk25/src/com/android/appSdk25/Sdk25MaxAspectRatioActivity.java
deleted file mode 100644
index 7fa25e0..0000000
--- a/tests/app/appSdk25/src/com/android/appSdk25/Sdk25MaxAspectRatioActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.appSdk25;
-
-import android.app.Activity;
-
-public class Sdk25MaxAspectRatioActivity extends Activity {
-
-}
diff --git a/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java b/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java
index 601dafd..4192883 100644
--- a/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java
+++ b/tests/app/src/android/app/cts/ActivityKeyboardShortcutsTest.java
@@ -56,15 +56,15 @@
return;
}
// Open activity's options menu
- mActivity.openOptionsMenu();
+ getInstrumentation().runOnMainSync(() -> mActivity.openOptionsMenu());
mActivity.waitForMenuToBeOpen();
// Request keyboard shortcuts
- mActivity.requestShowKeyboardShortcuts();
+ getInstrumentation().runOnMainSync(() -> mActivity.requestShowKeyboardShortcuts());
mActivity.waitForKeyboardShortcutsToBeRequested();
// Close the shortcuts helper
- mActivity.dismissKeyboardShortcutsHelper();
+ getInstrumentation().runOnMainSync(() -> mActivity.dismissKeyboardShortcutsHelper());
// THEN the activity's onProvideKeyboardShortcuts should have been
// triggered to get app specific shortcuts
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 7c1ed0d..2e6867d 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -17,6 +17,7 @@
package android.app.cts;
import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Instrumentation;
@@ -27,6 +28,7 @@
import android.app.cts.android.app.cts.tools.UidImportanceListener;
import android.app.cts.android.app.cts.tools.WaitForBroadcast;
import android.app.cts.android.app.cts.tools.WatchUidRunner;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -34,11 +36,20 @@
import android.os.Parcel;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.server.am.WindowManagerState;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiSelector;
import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
import com.android.compatibility.common.util.SystemUtil;
public class ActivityManagerProcessStateTest extends InstrumentationTestCase {
+ private static final String TAG = ActivityManagerProcessStateTest.class.getName();
+
private static final String STUB_PACKAGE_NAME = "android.app.stubs";
private static final int WAIT_TIME = 2000;
// A secondary test activity from another APK.
@@ -50,6 +61,12 @@
public static String ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT =
"com.android.cts.launcherapps.simpleapp.SimpleActivityStartService.RESULT";
+ // APKs for testing heavy weight app interactions.
+ static final String CANT_SAVE_STATE_1_PACKAGE_NAME = "com.android.test.cantsavestate1";
+ static final String CANT_SAVE_STATE_2_PACKAGE_NAME = "com.android.test.cantsavestate2";
+
+ private static final int TEMP_WHITELIST_DURATION_MS = 2000;
+
private Context mContext;
private Instrumentation mInstrumentation;
private Intent mServiceIntent;
@@ -70,11 +87,75 @@
mAllProcesses[1] = mService2Intent;
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
+ removeTestAppFromWhitelists();
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ private void removeTestAppFromWhitelists() throws Exception {
+ executeShellCmd("cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME);
+ executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
+ }
+
+ private String executeShellCmd(String cmd) throws Exception {
+ final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
+ return result;
+ }
+
+ private boolean isScreenInteractive() {
+ final PowerManager powerManager =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ return powerManager.isInteractive();
+ }
+
+ private boolean isKeyguardLocked() {
+ final KeyguardManager keyguardManager =
+ (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ return keyguardManager.isKeyguardLocked();
+ }
+
+ private void waitForAppFocus(String waitForApp, long waitTime) {
+ long waitUntil = SystemClock.elapsedRealtime() + waitTime;
+ while (true) {
+ WindowManagerState wms = new WindowManagerState();
+ wms.computeState();
+ String appName = wms.getFocusedApp();
+ if (appName != null) {
+ ComponentName comp = ComponentName.unflattenFromString(appName);
+ if (waitForApp.equals(comp.getPackageName())) {
+ break;
+ }
+ }
+ if (SystemClock.elapsedRealtime() > waitUntil) {
+ throw new IllegalStateException("Timed out waiting for focus on app "
+ + waitForApp + ", last was " + appName);
+ }
+ Log.i(TAG, "Waiting for app focus, current: " + appName);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ };
+ }
+
+ private void startActivityAndWaitForShow(final Intent intent) throws Exception {
+ getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+ () -> {
+ try {
+ mContext.startActivity(intent);
+ } catch (Exception e) {
+ fail("Cannot start activity: " + intent);
+ }
+ }, (AccessibilityEvent event) -> event.getEventType()
+ == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ , WAIT_TIME);
+ }
+
+ private void maybeClick(UiDevice device, UiSelector sel) {
+ try { device.findObject(sel).click(); } catch (Throwable ignored) { }
+ }
+
+ private void maybeClick(UiDevice device, BySelector sel) {
+ try { device.findObject(sel).click(); } catch (Throwable ignored) { }
}
/**
@@ -82,14 +163,17 @@
*/
public void testUidImportanceListener() throws Exception {
final Parcel data = Parcel.obtain();
- ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent);
- ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent);
+ ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
+ WAIT_TIME);
+ ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent,
+ WAIT_TIME);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
SIMPLE_PACKAGE_NAME, 0);
- UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid);
+ UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid,
+ WAIT_TIME);
String cmd = "pm revoke " + STUB_PACKAGE_NAME + " "
+ Manifest.permission.PACKAGE_USAGE_STATS;
@@ -119,20 +203,21 @@
am.addOnUidImportanceListener(uidForegroundListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
- UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
+ UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid, WAIT_TIME);
am.addOnUidImportanceListener(uidGoneListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid);
+ WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WAIT_TIME);
try {
// First kill the processes to start out in a stable state.
- conn.bind(WAIT_TIME);
- conn2.bind(WAIT_TIME);
+ conn.bind();
+ conn2.bind();
IBinder service1 = conn.getServiceIBinder();
IBinder service2 = conn2.getServiceIBinder();
- conn.unbind(WAIT_TIME);
- conn2.unbind(WAIT_TIME);
+ conn.unbind();
+ conn2.unbind();
try {
service1.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
@@ -145,38 +230,38 @@
// Wait for uid's processes to go away.
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And wait for the uid report to be gone.
- uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
// Now bind and see if we get told about the uid coming in to the foreground.
- conn.bind(WAIT_TIME);
+ conn.bind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Also make sure the uid state reports are as expected. Wait for active because
// there may be some intermediate states as the process comes up.
- uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Pull out the service IBinder for a kludy hack...
IBinder service = conn.getServiceIBinder();
// Now unbind and see if we get told about it going to the background.
- conn.unbind(WAIT_TIME);
+ conn.unbind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Now kill the process and see if we are told about it being gone.
try {
@@ -186,74 +271,74 @@
}
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+ uidWatcher.expect(WatchUidRunner.CMD_GONE, null);
// Now we are going to try different combinations of binding to two processes to
// see if they are correctly combined together for the app.
// Bring up both services.
- conn.bind(WAIT_TIME);
- conn2.bind(WAIT_TIME);
+ conn.bind();
+ conn2.bind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Also make sure the uid state reports are as expected.
- uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Bring down one service, app state should remain foreground.
- conn2.unbind(WAIT_TIME);
+ conn2.unbind();
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Bring down other service, app state should now be cached. (If the processes both
// actually get killed immediately, this is also not a correctly behaving system.)
- conn.unbind(WAIT_TIME);
+ conn.unbind();
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Bring up one service, this should be sufficient to become foreground.
- conn2.bind(WAIT_TIME);
+ conn2.bind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Bring up other service, should remain foreground.
- conn.bind(WAIT_TIME);
+ conn.bind();
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Bring down one service, should remain foreground.
- conn.unbind(WAIT_TIME);
+ conn.unbind();
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And bringing down other service should put us back to cached.
- conn2.unbind(WAIT_TIME);
+ conn2.unbind();
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
data.recycle();
@@ -273,7 +358,8 @@
Intent serviceIntent = new Intent();
serviceIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE);
- ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent);
+ ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent,
+ WAIT_TIME);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
@@ -290,20 +376,22 @@
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
SIMPLE_PACKAGE_NAME, 0);
- UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid);
+ UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid,
+ WAIT_TIME);
am.addOnUidImportanceListener(uidForegroundListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
- UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
+ UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid, WAIT_TIME);
am.addOnUidImportanceListener(uidGoneListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY);
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid);
+ WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WAIT_TIME);
// First kill the process to start out in a stable state.
mContext.stopService(serviceIntent);
- conn.bind(WAIT_TIME);
+ conn.bind();
IBinder service = conn.getServiceIBinder();
- conn.unbind(WAIT_TIME);
+ conn.unbind();
try {
service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
@@ -312,19 +400,19 @@
// Wait for uid's process to go away.
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And wait for the uid report to be gone.
- uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// This is a side-effect of the app op command.
- uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE");
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
@@ -350,37 +438,38 @@
}
// Put app on temporary whitelist to see if this allows the service start.
- cmd = "cmd deviceidle tempwhitelist -d 2000 " + SIMPLE_PACKAGE_NAME;
+ cmd = String.format("cmd deviceidle tempwhitelist -d %d %s",
+ TEMP_WHITELIST_DURATION_MS, SIMPLE_PACKAGE_NAME);
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// Try starting the service now that the app is whitelisted... should work!
mContext.startService(serviceIntent);
- conn.waitForConnect(WAIT_TIME);
+ conn.waitForConnect();
// Also make sure the uid state reports are as expected.
- uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and give enough time to get off the temp whitelist.
mContext.stopService(serviceIntent);
- conn.waitForDisconnect(WAIT_TIME);
+ conn.waitForDisconnect();
- uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
- Thread.sleep(3000);
+ executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
// Going off the temp whitelist causes a spurious proc state report... that's
// not ideal, but okay.
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
- uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Now that we should be off the temp whitelist, make sure we again can't start.
failed = false;
@@ -399,17 +488,17 @@
// Try starting the service now that the app is whitelisted... should work!
mContext.startService(serviceIntent);
- conn.waitForConnect(WAIT_TIME);
+ conn.waitForConnect();
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Okay, bring down the service.
mContext.stopService(serviceIntent);
- conn.waitForDisconnect(WAIT_TIME);
+ conn.waitForDisconnect();
- uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
mContext.stopService(serviceIntent);
@@ -435,8 +524,10 @@
*/
public void testBackgroundCheckStopsService() throws Exception {
final Parcel data = Parcel.obtain();
- ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent);
- ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent);
+ ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
+ WAIT_TIME);
+ ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent,
+ WAIT_TIME);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
@@ -453,24 +544,26 @@
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
SIMPLE_PACKAGE_NAME, 0);
- UidImportanceListener uidServiceListener = new UidImportanceListener(appInfo.uid);
+ UidImportanceListener uidServiceListener = new UidImportanceListener(appInfo.uid,
+ WAIT_TIME);
am.addOnUidImportanceListener(uidServiceListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
- UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid);
+ UidImportanceListener uidGoneListener = new UidImportanceListener(appInfo.uid, WAIT_TIME);
am.addOnUidImportanceListener(uidGoneListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
- WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid);
+ WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WAIT_TIME);
// First kill the process to start out in a stable state.
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
- conn.bind(WAIT_TIME);
- conn2.bind(WAIT_TIME);
+ conn.bind();
+ conn2.bind();
IBinder service = conn.getServiceIBinder();
IBinder service2 = conn2.getServiceIBinder();
- conn.unbind(WAIT_TIME);
- conn2.unbind(WAIT_TIME);
+ conn.unbind();
+ conn2.unbind();
try {
service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
@@ -483,7 +576,7 @@
// Wait for uid's process to go away.
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
@@ -494,8 +587,8 @@
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// This is a side-effect of the app op command.
- uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_NONEXISTENT);
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
@@ -521,43 +614,43 @@
}
// First poke the process into the foreground, so we can avoid background check.
- conn2.bind(WAIT_TIME);
- conn2.waitForConnect(WAIT_TIME);
+ conn2.bind();
+ conn2.waitForConnect();
// Wait for process state to reflect running service.
uidServiceListener.waitForValue(
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Also make sure the uid state reports are as expected.
- uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
- conn2.unbind(WAIT_TIME);
+ conn2.unbind();
// Wait for process to recover back down to being cached.
uidServiceListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Try starting the service now that the app is waiting to idle... should work!
mContext.startService(mServiceIntent);
- conn.waitForConnect(WAIT_TIME);
+ conn.waitForConnect();
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// And also start the second service.
conn2.startMonitoring();
mContext.startService(mService2Intent);
- conn2.waitForConnect(WAIT_TIME);
+ conn2.waitForConnect();
// Force app to go idle now
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
@@ -565,24 +658,24 @@
// Wait for services to be stopped by system.
uidServiceListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_TIME);
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And service should be stopped by system, so just make sure it is disconnected.
- conn.waitForDisconnect(WAIT_TIME);
- conn2.waitForDisconnect(WAIT_TIME);
+ conn.waitForDisconnect();
+ conn2.waitForDisconnect();
- uidWatcher.expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// There may be a transient 'SVC' proc state here.
- uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
- conn.cleanup(WAIT_TIME);
- conn2.cleanup(WAIT_TIME);
+ conn.cleanup();
+ conn2.cleanup();
uidWatcher.finish();
@@ -609,16 +702,17 @@
SIMPLE_PACKAGE_NAME + SIMPLE_RECEIVER_START_SERVICE);
final ServiceProcessController controller = new ServiceProcessController(mContext,
- getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
+ getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
- mServiceIntent);
+ mServiceIntent, WAIT_TIME);
+ final WatchUidRunner uidWatcher = controller.getUidWatcher();
try {
// First kill the process to start out in a stable state.
- controller.ensureProcessGone(WAIT_TIME);
+ controller.ensureProcessGone();
// Do initial setup.
- controller.denyBackgroundOp(WAIT_TIME);
+ controller.denyBackgroundOp();
controller.makeUidIdle();
controller.removeFromWhitelist();
@@ -635,18 +729,18 @@
}
// Track the uid proc state changes from the broadcast (but not service execution)
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "RCVR", WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_RECEIVER, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY, WAIT_TIME);
// Put app on temporary whitelist to see if this allows the service start.
- controller.tempWhitelist(2000);
+ controller.tempWhitelist(TEMP_WHITELIST_DURATION_MS);
// Being on the whitelist means the uid is now active.
- controller.getUidWatcher().expect(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY, WAIT_TIME);
// Try starting the service now that the app is whitelisted... should work!
br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
@@ -654,34 +748,34 @@
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
- conn.waitForConnect(WAIT_TIME);
+ conn.waitForConnect();
// Also make sure the uid state reports are as expected.
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
// We are going to wait until 'SVC', because we may see an intermediate 'RCVR'
// proc state depending on timing.
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and give enough time to get off the temp whitelist.
mContext.stopService(mServiceIntent);
- conn.waitForDisconnect(WAIT_TIME);
+ conn.waitForDisconnect();
- controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
- Thread.sleep(3000);
+ controller.removeFromTempWhitelist();
// Going off the temp whitelist causes a spurious proc state report... that's
// not ideal, but okay.
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// We don't want to wait for the uid to actually go idle, we can force it now.
controller.makeUidIdle();
- controller.getUidWatcher().expect(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// Make sure the process is gone so we start over fresh.
- controller.ensureProcessGone(WAIT_TIME);
+ controller.ensureProcessGone();
// Now that we should be off the temp whitelist, make sure we again can't start.
br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
@@ -691,13 +785,13 @@
}
// Track the uid proc state changes from the broadcast (but not service execution)
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null);
// There could be a transient 'cached' state here before 'uncached' if uid state
// changes are dispatched before receiver is started.
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "RCVR", WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_RECEIVER);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Now put app on whitelist, should allow service to run.
controller.addToWhitelist();
@@ -708,18 +802,18 @@
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
- conn.waitForConnect(WAIT_TIME);
+ conn.waitForConnect();
// Also make sure the uid state reports are as expected.
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Okay, bring down the service.
mContext.stopService(mServiceIntent);
- conn.waitForDisconnect(WAIT_TIME);
+ conn.waitForDisconnect();
- controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
mContext.stopService(mServiceIntent);
@@ -738,16 +832,17 @@
SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY_START_SERVICE);
final ServiceProcessController controller = new ServiceProcessController(mContext,
- getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
+ getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
- mServiceIntent);
+ mServiceIntent, WAIT_TIME);
+ final WatchUidRunner uidWatcher = controller.getUidWatcher();
try {
// First kill the process to start out in a stable state.
- controller.ensureProcessGone(WAIT_TIME);
+ controller.ensureProcessGone();
// Do initial setup.
- controller.denyBackgroundOp(WAIT_TIME);
+ controller.denyBackgroundOp();
controller.makeUidIdle();
controller.removeFromWhitelist();
@@ -764,37 +859,36 @@
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
- conn.waitForConnect(WAIT_TIME);
+ conn.waitForConnect();
final String expectedActivityState = (isScreenInteractive() && !isKeyguardLocked())
- ? "TOP" : "TPSL";
+ ? WatchUidRunner.STATE_TOP : WatchUidRunner.STATE_TOP_SLEEPING;
// Also make sure the uid state reports are as expected.
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE,
- expectedActivityState, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, expectedActivityState);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Okay, bring down the service.
mContext.stopService(mServiceIntent);
- conn.waitForDisconnect(WAIT_TIME);
+ conn.waitForDisconnect();
- controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// App isn't yet idle, so we should be able to start the service again.
mContext.startService(mServiceIntent);
- conn.waitForConnect(WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
+ conn.waitForConnect();
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// And now fast-forward to the app going idle, service should be stopped.
controller.makeUidIdle();
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null);
- conn.waitForDisconnect(WAIT_TIME);
- controller.getUidWatcher().waitFor(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
- controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
+ conn.waitForDisconnect();
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// No longer should be able to start service.
boolean failed = false;
@@ -814,15 +908,271 @@
}
}
- private boolean isScreenInteractive() {
- final PowerManager powerManager =
- (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- return powerManager.isInteractive();
+ /**
+ * Test that a single "can't save state" app has the proper process management
+ * semantics.
+ */
+ public void testCantSaveStateLaunchAndBackground() throws Exception {
+ final Intent activityIntent = new Intent();
+ activityIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+ activityIntent.setAction(Intent.ACTION_MAIN);
+ activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final Intent homeIntent = new Intent();
+ homeIntent.setAction(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
+
+ String cmd = "pm grant " + STUB_PACKAGE_NAME + " "
+ + Manifest.permission.PACKAGE_USAGE_STATS;
+ String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+ // We don't want to wait for the uid to actually go idle, we can force it now.
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+ ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
+ CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
+
+ // This test is also using UidImportanceListener to make sure the correct
+ // heavy-weight state is reported there.
+ UidImportanceListener uidForegroundListener = new UidImportanceListener(appInfo.uid,
+ WAIT_TIME);
+ am.addOnUidImportanceListener(uidForegroundListener,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+ UidImportanceListener uidBackgroundListener = new UidImportanceListener(appInfo.uid,
+ WAIT_TIME);
+ am.addOnUidImportanceListener(uidBackgroundListener,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE-1);
+
+ WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+ WAIT_TIME);
+
+ try {
+ // Start the heavy-weight app, should launch like a normal app.
+ mContext.startActivity(activityIntent);
+
+ // Wait for process state to reflect running activity.
+ uidForegroundListener.waitForValue(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+ assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+ // Also make sure the uid state reports are as expected.
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Now go to home, leaving the app. It should be put in the heavy weight state.
+ mContext.startActivity(homeIntent);
+
+ // Wait for process to go down to background heavy-weight.
+ uidBackgroundListener.waitForValue(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE);
+ assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE,
+ am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+ // While in background, should go in to normal idle state.
+ // Force app to go idle now
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+
+ // Switch back to heavy-weight app to see if it correctly returns to foreground.
+ mContext.startActivity(activityIntent);
+
+ // Wait for process state to reflect running activity.
+ uidForegroundListener.waitForValue(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+ assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
+ am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+ // Also make sure the uid state reports are as expected.
+ uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
+
+ // Exit activity, check to see if we are now cached.
+ getInstrumentation().getUiAutomation().performGlobalAction(
+ AccessibilityService.GLOBAL_ACTION_BACK);
+
+ // Wait for process to become cached
+ uidBackgroundListener.waitForValue(
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+ assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
+ am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
+
+ uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+ // While in background, should go in to normal idle state.
+ // Force app to go idle now
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
+
+ } finally {
+ uidWatcher.finish();
+
+ am.removeOnUidImportanceListener(uidForegroundListener);
+ am.removeOnUidImportanceListener(uidBackgroundListener);
+ }
}
- private boolean isKeyguardLocked() {
- final KeyguardManager keyguardManager =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- return keyguardManager.isKeyguardLocked();
+ /**
+ * Test that switching between two "can't save state" apps is handled properly.
+ */
+ public void testCantSaveStateLaunchAndSwitch() throws Exception {
+ final Intent activity1Intent = new Intent();
+ activity1Intent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+ activity1Intent.setAction(Intent.ACTION_MAIN);
+ activity1Intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ activity1Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final Intent activity2Intent = new Intent();
+ activity2Intent.setPackage(CANT_SAVE_STATE_2_PACKAGE_NAME);
+ activity2Intent.setAction(Intent.ACTION_MAIN);
+ activity2Intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ activity2Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final Intent homeIntent = new Intent();
+ homeIntent.setAction(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
+ UiDevice device = UiDevice.getInstance(getInstrumentation());
+
+ String cmd = "pm grant " + STUB_PACKAGE_NAME + " "
+ + Manifest.permission.PACKAGE_USAGE_STATS;
+ String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+ // We don't want to wait for the uid to actually go idle, we can force it now.
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+ ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+ CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
+ WatchUidRunner uid1Watcher = new WatchUidRunner(getInstrumentation(), app1Info.uid,
+ WAIT_TIME);
+
+ ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+ CANT_SAVE_STATE_2_PACKAGE_NAME, 0);
+ WatchUidRunner uid2Watcher = new WatchUidRunner(getInstrumentation(), app2Info.uid,
+ WAIT_TIME);
+
+ try {
+ // Start the first heavy-weight app, should launch like a normal app.
+ mContext.startActivity(activity1Intent);
+
+ // Make sure the uid state reports are as expected.
+ uid1Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Now go to home, leaving the app. It should be put in the heavy weight state.
+ mContext.startActivity(homeIntent);
+
+ // Wait for process to go down to background heavy-weight.
+ uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+ // Start the second heavy-weight app, should ask us what to do with the two apps
+ startActivityAndWaitForShow(activity2Intent);
+
+ // First, let's try returning to the original app.
+ maybeClick(device, new UiSelector().resourceId("android:id/switch_old"));
+ device.waitForIdle();
+
+ // App should now be back in foreground.
+ uid1Watcher.expect(WatchUidRunner.CMD_UNCACHED, null);
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Return to home.
+ mContext.startActivity(homeIntent);
+ uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+ // Again try starting second heavy-weight app to get prompt.
+ startActivityAndWaitForShow(activity2Intent);
+
+ // Now we'll switch to the new app.
+ maybeClick(device, new UiSelector().resourceId("android:id/switch_new"));
+ device.waitForIdle();
+
+ // The original app should now become cached.
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+ // And the new app should start.
+ uid2Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Make sure the original app is idle for cleanliness
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ uid1Watcher.expect(WatchUidRunner.CMD_IDLE, null);
+
+ // Return to home.
+ mContext.startActivity(homeIntent);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+ // Try starting the first heavy weight app, but return to the existing second.
+ startActivityAndWaitForShow(activity1Intent);
+ maybeClick(device, new UiSelector().resourceId("android:id/switch_old"));
+ device.waitForIdle();
+ uid2Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Return to home.
+ mContext.startActivity(homeIntent);
+ uid2Watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
+
+ // Again start the first heavy weight app, this time actually switching to it
+ startActivityAndWaitForShow(activity1Intent);
+ maybeClick(device, new UiSelector().resourceId("android:id/switch_new"));
+ device.waitForIdle();
+
+ // The second app should now become cached.
+ uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+ // And the first app should start.
+ uid1Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
+ uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Exit activity, check to see if we are now cached.
+ waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
+ getInstrumentation().getUiAutomation().performGlobalAction(
+ AccessibilityService.GLOBAL_ACTION_BACK);
+ uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
+ uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+
+ // Make both apps idle for cleanliness.
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+ cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
+ result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+
+ } finally {
+ uid2Watcher.finish();
+ uid1Watcher.finish();
+ }
}
}
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index e718a32..5e77221 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -369,7 +369,6 @@
final RunningAppProcessInfo ra = new RunningAppProcessInfo();
ActivityManager.getMyMemoryState(ra);
- assertEquals(mContext.getApplicationInfo().processName, ra.processName);
assertEquals(android.os.Process.myUid(), ra.uid);
// When an instrumentation test is running, the importance is high.
diff --git a/tests/app/src/android/app/cts/AlertWindowsTests.java b/tests/app/src/android/app/cts/AlertWindowsTests.java
deleted file mode 100644
index b6b4ce4..0000000
--- a/tests/app/src/android/app/cts/AlertWindowsTests.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.cts;
-
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE_PRE_26;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
-import static android.content.Context.BIND_ALLOW_OOM_MANAGEMENT;
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-
-import static com.android.app2.AlertWindowService.MSG_ADD_ALERT_WINDOW;
-import static com.android.app2.AlertWindowService.MSG_ON_ALERT_WINDOW_ADDED;
-import static com.android.app2.AlertWindowService.MSG_ON_ALERT_WINDOW_REMOVED;
-import static com.android.app2.AlertWindowService.MSG_REMOVE_ALERT_WINDOW;
-import static com.android.app2.AlertWindowService.MSG_REMOVE_ALL_ALERT_WINDOWS;
-import static com.android.app2.AlertWindowService.NOTIFICATION_MESSENGER_EXTRA;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-
-import com.android.app2.AlertWindowService;
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-/**
- * Build: mmma -j32 cts/tests/app
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsAppTestCases android.app.cts.AlertWindowsTests
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AlertWindowsTests {
-
- private static final String TAG = "AlertWindowsTests";
-
- private static final boolean DEBUG = false;
- private static final long WAIT_TIME_MS = 2 * 1000;
-
- private static final String SDK25_PACKAGE_NAME = "com.android.appSdk25";
-
- private Messenger mService;
- private String mServicePackageName;
- private int mServiceUid;
-
- private PackageManager mPm;
-
- private ActivityManager mAm;
- private ActivityManager mAm25; // ActivityManager created for an SDK 25 app context.
-
- private final Messenger mMessenger = new Messenger(new IncomingHandler(Looper.getMainLooper()));
- private final Object mAddedLock = new Object();
- private final Object mRemoveLock = new Object();
-
- @Before
- public void setUp() throws Exception {
- if (DEBUG) Log.e(TAG, "setUp");
- final Context context = InstrumentationRegistry.getTargetContext();
-
- mPm = context.getPackageManager();
-
- mAm = context.getSystemService(ActivityManager.class);
- mAm25 = context.createPackageContext(SDK25_PACKAGE_NAME, 0)
- .getSystemService(ActivityManager.class);
-
- final Intent intent = new Intent();
- intent.setClassName(AlertWindowService.class.getPackage().getName(),
- AlertWindowService.class.getName());
- intent.putExtra(NOTIFICATION_MESSENGER_EXTRA, mMessenger);
- // Needs to be both BIND_NOT_FOREGROUND and BIND_ALLOW_OOM_MANAGEMENT to avoid the binding
- // to this instrumentation test from increasing its importance.
- context.bindService(intent, mConnection,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_ALLOW_OOM_MANAGEMENT);
- synchronized (mConnection) {
- // Wait for alert window service to be connection before processing.
- mConnection.wait(WAIT_TIME_MS);
- }
- }
-
- @After
- public void tearDown() throws Exception {
- if (DEBUG) Log.e(TAG, "tearDown");
- if (mService != null) {
- mService.send(Message.obtain(null, MSG_REMOVE_ALL_ALERT_WINDOWS));
- }
- final Context context = InstrumentationRegistry.getTargetContext();
- context.unbindService(mConnection);
- mAm = null;
- }
-
- @Test
- public void testAlertWindowOomAdj() throws Exception {
- // Alert windows are always hidden when running in VR.
- if (isRunningInVR()) {
- return;
- }
- setAlertWindowPermission(true /* allow */);
-
-
- assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-
- // TODO AM.getUidImportance() sometimes return a different value from what
- // getPackageImportance() returns... b/37950472
- // assertUidImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-
- addAlertWindow();
- // Process importance should be increased to visible when the service has an alert window.
- assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
- addAlertWindow();
- assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
- setAlertWindowPermission(false /* allow */);
- // Process importance should no longer be visible since its alert windows are not allowed to
- // be visible.
- assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
-
- setAlertWindowPermission(true /* allow */);
- // They can show again so importance should be visible again.
- assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
- removeAlertWindow();
- assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
-
- removeAlertWindow();
- // Process importance should no longer be visible when the service no longer as alert
- // windows.
- assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
- }
-
- private void addAlertWindow() throws Exception {
- mService.send(Message.obtain(null, MSG_ADD_ALERT_WINDOW));
- synchronized (mAddedLock) {
- // Wait for window addition confirmation before proceeding.
- mAddedLock.wait(WAIT_TIME_MS);
- }
- }
-
- private void removeAlertWindow() throws Exception {
- mService.send(Message.obtain(null, MSG_REMOVE_ALERT_WINDOW));
- synchronized (mRemoveLock) {
- // Wait for window removal confirmation before proceeding.
- mRemoveLock.wait(WAIT_TIME_MS);
- }
- }
-
- private void setAlertWindowPermission(boolean allow) throws Exception {
- final String cmd = "appops set " + mServicePackageName
- + " android:system_alert_window " + (allow ? "allow" : "deny");
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
- }
-
- private void assertImportance(Function<ActivityManager, Integer> apiCaller,
- int expectedForO, int expectedForPreO) throws Exception {
- final long TIMEOUT = SystemClock.uptimeMillis() + TimeUnit.SECONDS.toMillis(30);
- int actual;
-
- do {
- // TODO: We should try to use ActivityManagerTest.UidImportanceListener here to listen
- // for changes in the uid importance. However, the way it is currently structured
- // doesn't really work for this use case right now...
- Thread.sleep(500);
- actual = apiCaller.apply(mAm);
- } while (actual != expectedForO && (SystemClock.uptimeMillis() < TIMEOUT));
-
- assertEquals(expectedForO, actual);
-
- // Check the result for pre-O apps.
- assertEquals(expectedForPreO, (int) apiCaller.apply(mAm25));
- }
-
- /**
- * Make sure {@link ActivityManager#getPackageImportance} returns the expected value.
- */
- private void assertPackageImportance(int expectedForO, int expectedForPreO) throws Exception {
- assertImportance(am -> am.getPackageImportance(mServicePackageName),
- expectedForO, expectedForPreO);
- }
-
- /**
- * Make sure {@link ActivityManager#getUidImportance(int)} returns the expected value.
- */
- private void assertUidImportance(int expectedForO, int expectedForPreO) throws Exception {
- assertImportance(am -> am.getUidImportance(mServiceUid),
- expectedForO, expectedForPreO);
- }
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Log.e(TAG, "onServiceConnected");
- mService = new Messenger(service);
- mServicePackageName = name.getPackageName();
- try {
- mServiceUid = mPm.getPackageUid(mServicePackageName, 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException("getPackageUid() failed.", e);
- }
- synchronized (mConnection) {
- notifyAll();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Log.e(TAG, "onServiceDisconnected");
- mService = null;
- mServicePackageName = null;
- mServiceUid = 0;
- }
- };
-
- private class IncomingHandler extends Handler {
-
- IncomingHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ON_ALERT_WINDOW_ADDED:
- synchronized (mAddedLock) {
- if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_ADDED");
- mAddedLock.notifyAll();
- }
- break;
- case MSG_ON_ALERT_WINDOW_REMOVED:
- synchronized (mRemoveLock) {
- if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_REMOVED");
- mRemoveLock.notifyAll();
- }
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
-
- private boolean isRunningInVR() {
- final Context context = InstrumentationRegistry.getTargetContext();
- if ((context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
- == Configuration.UI_MODE_TYPE_VR_HEADSET) {
- return true;
- }
- return false;
- }
-}
diff --git a/tests/app/src/android/app/cts/AspectRatioTests.java b/tests/app/src/android/app/cts/AspectRatioTests.java
deleted file mode 100644
index fa6154e..0000000
--- a/tests/app/src/android/app/cts/AspectRatioTests.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.cts;
-
-import android.app.stubs.MetaDataMaxAspectRatioActivity;
-import com.android.appSdk25.Sdk25MaxAspectRatioActivity;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.app.Activity;
-import android.app.stubs.MaxAspectRatioActivity;
-import android.app.stubs.MaxAspectRatioResizeableActivity;
-import android.app.stubs.MaxAspectRatioUnsetActivity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import static android.content.Context.WINDOW_SERVICE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static org.junit.Assert.fail;
-
-/**
- * Build: mmma -j32 cts/tests/app
- * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsAppTestCases android.app.cts.AspectRatioTests
- */
-@RunWith(AndroidJUnit4.class)
-public class AspectRatioTests {
- private static final String TAG = "AspectRatioTests";
-
- // The max. aspect ratio the test activities are using.
- private static final float MAX_ASPECT_RATIO = 1.0f;
-
- // Max supported aspect ratio for pre-O apps.
- private static final float MAX_PRE_O_ASPECT_RATIO = 1.86f;
-
- // The minimum supported device aspect ratio.
- private static final float MIN_DEVICE_ASPECT_RATIO = 1.333f;
-
- // The minimum supported device aspect ratio for watches.
- private static final float MIN_WATCH_DEVICE_ASPECT_RATIO = 1.0f;
-
- @Rule
- public ActivityTestRule<MaxAspectRatioActivity> mMaxAspectRatioActivity =
- new ActivityTestRule<>(MaxAspectRatioActivity.class,
- false /* initialTouchMode */, false /* launchActivity */);
-
- @Rule
- public ActivityTestRule<MaxAspectRatioResizeableActivity> mMaxAspectRatioResizeableActivity =
- new ActivityTestRule<>(MaxAspectRatioResizeableActivity.class,
- false /* initialTouchMode */, false /* launchActivity */);
-
- @Rule
- public ActivityTestRule<MetaDataMaxAspectRatioActivity> mMetaDataMaxAspectRatioActivity =
- new ActivityTestRule<>(MetaDataMaxAspectRatioActivity.class,
- false /* initialTouchMode */, false /* launchActivity */);
-
- @Rule
- public ActivityTestRule<MaxAspectRatioUnsetActivity> mMaxAspectRatioUnsetActivity =
- new ActivityTestRule<>(MaxAspectRatioUnsetActivity.class,
- false /* initialTouchMode */, false /* launchActivity */);
-
- // TODO: Can't use this to start an activity in a different process...sigh.
- @Rule
- public ActivityTestRule<Sdk25MaxAspectRatioActivity> mSdk25MaxAspectRatioActivity =
- new ActivityTestRule<>(Sdk25MaxAspectRatioActivity.class, "com.android.appSdk25",
- 268435456, false /* initialTouchMode */, false /* launchActivity */);
-
- private interface AssertAspectRatioCallback {
- void assertAspectRatio(float actual);
- }
-
- @Before
- public void setUp() throws Exception {
-
- }
-
- @After
- public void tearDown() throws Exception {
- finishActivity(mMaxAspectRatioActivity);
- finishActivity(mMaxAspectRatioResizeableActivity);
- finishActivity(mSdk25MaxAspectRatioActivity);
- finishActivity(mMaxAspectRatioUnsetActivity);
- finishActivity(mMetaDataMaxAspectRatioActivity);
- }
-
- @Test
- @Presubmit
- public void testDeviceAspectRatio() throws Exception {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
- final Display display = wm.getDefaultDisplay();
- final DisplayMetrics metrics = new DisplayMetrics();
- display.getRealMetrics(metrics);
-
- float longSide = Math.max(metrics.widthPixels, metrics.heightPixels);
- float shortSide = Math.min(metrics.widthPixels, metrics.heightPixels);
- float deviceAspectRatio = longSide / shortSide;
- float expectedMinAspectRatio = context.getPackageManager().hasSystemFeature(FEATURE_WATCH)
- ? MIN_WATCH_DEVICE_ASPECT_RATIO : MIN_DEVICE_ASPECT_RATIO;
-
- if (deviceAspectRatio < expectedMinAspectRatio) {
- fail("deviceAspectRatio=" + deviceAspectRatio
- + " is less than expectedMinAspectRatio=" + expectedMinAspectRatio);
- }
- }
-
- @Test
- @Presubmit
- public void testMaxAspectRatio() throws Exception {
- runTest(launchActivity(mMaxAspectRatioActivity),
- actual -> {
- if (MAX_ASPECT_RATIO >= actual) return;
- fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
- });
- }
-
- @Test
- @Presubmit
- public void testMetaDataMaxAspectRatio() throws Exception {
- runTest(launchActivity(mMetaDataMaxAspectRatioActivity),
- actual -> {
- if (MAX_ASPECT_RATIO >= actual) return;
- fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
- });
- }
-
- @Test
- // TODO: Currently 10% flaky so not part of pre-submit for now
- public void testMaxAspectRatioResizeableActivity() throws Exception {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final float expected = getAspectRatio(context);
- final Activity testActivity = launchActivity(mMaxAspectRatioResizeableActivity);
- PollingCheck.waitFor(testActivity::hasWindowFocus);
-
- Display testDisplay = testActivity.findViewById(android.R.id.content).getDisplay();
-
- // TODO(b/69982434): Fix DisplayManager NPE when getting display from Instrumentation
- // context, then can use DisplayManager to get the aspect ratio of the correct display.
- if (testDisplay.getDisplayId() != Display.DEFAULT_DISPLAY) {
- return;
- }
-
- // Since this activity is resizeable, its aspect ratio shouldn't be less than the device's
- runTest(testActivity,
- actual -> {
- if (aspectRatioEqual(expected, actual) || expected < actual) return;
- fail("actual=" + actual + " is less than expected=" + expected);
- });
- }
-
- @Test
- @Presubmit
- public void testMaxAspectRatioUnsetActivity() throws Exception {
- final Context context = InstrumentationRegistry.getInstrumentation().getContext();
- final float expected = getAspectRatio(context);
-
- // Since this activity didn't set an aspect ratio, its aspect ratio shouldn't be less than
- // the device's
- runTest(launchActivity(mMaxAspectRatioUnsetActivity),
- actual -> {
- if (aspectRatioEqual(expected, actual) || expected < actual) return;
- fail("actual=" + actual + " is less than expected=" + expected);
- });
- }
-
- @Test
- // TODO(b/35810513): Can't use rule to start an activity in a different process. Need a
- // different way to make this test happen...host side? Sigh...
- @Ignore
- public void testMaxAspectRatioPreOActivity() throws Exception {
- runTest(launchActivity(mSdk25MaxAspectRatioActivity),
- actual -> {
- if (MAX_PRE_O_ASPECT_RATIO >= actual) return;
- fail("actual=" + actual + " is greater than expected=" + MAX_PRE_O_ASPECT_RATIO);
- });
- }
-
- private void runTest(Activity activity, AssertAspectRatioCallback callback) {
- callback.assertAspectRatio(getAspectRatio(activity));
-
- // TODO(b/35810513): All this rotation stuff doesn't really work yet. Need to make sure
- // context is updated correctly here. Also, what does it mean to be holding a reference to
- // this activity if changing the orientation will cause a relaunch?
-// activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
-// waitForIdle();
-// callback.assertAspectRatio(getAspectRatio(activity));
-//
-// activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
-// waitForIdle();
-// callback.assertAspectRatio(getAspectRatio(activity));
- }
-
- private float getAspectRatio(Context context) {
- final Display display =
- ((WindowManager) context.getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
- final Point size = new Point();
- display.getSize(size);
- final float longSide = Math.max(size.x, size.y);
- final float shortSide = Math.min(size.x, size.y);
- return longSide / shortSide;
- }
-
- private Activity launchActivity(ActivityTestRule activityRule) {
- final Activity activity = activityRule.launchActivity(null);
- waitForIdle();
- return activity;
- }
-
- private void finishActivity(ActivityTestRule activityRule) {
- final Activity activity = activityRule.getActivity();
- if (activity != null) {
- activity.finish();
- }
- }
-
- private void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- private static boolean aspectRatioEqual(float a, float b) {
- // Aspect ratios are considered equal if they ware within to significant digits.
- float diff = Math.abs(a - b);
- return diff < 0.01f;
- }
-}
diff --git a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
index 162815f..7931a1f 100644
--- a/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelGroupTest.java
@@ -37,10 +37,21 @@
NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
assertEquals("1", group.getId());
assertEquals("one", group.getName());
+ assertFalse(group.isBlocked());
+ assertNull(group.getDescription());
+ assertEquals(0, group.getChannels().size());
+ }
+
+ public void testIsBlocked() {
+ NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+ group.setBlocked(true);
+ assertTrue(group.isBlocked());
}
public void testWriteToParcel() {
NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+ group.setBlocked(true);
+ group.setDescription("bananas!");
Parcel parcel = Parcel.obtain();
group.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -51,8 +62,12 @@
public void testClone() {
NotificationChannelGroup group = new NotificationChannelGroup("1", "one");
+ group.setBlocked(true);
+ group.setDescription("bananas");
NotificationChannelGroup cloned = group.clone();
assertEquals("1", cloned.getId());
assertEquals("one", cloned.getName());
+ assertTrue(cloned.isBlocked());
+ assertEquals("bananas", cloned.getDescription());
}
}
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index b6077ca..7b716ac 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -80,6 +80,12 @@
}
mNotificationManager.deleteNotificationChannel(nc.getId());
}
+
+ List<NotificationChannelGroup> groups = mNotificationManager.getNotificationChannelGroups();
+ // Delete all groups.
+ for (NotificationChannelGroup ncg : groups) {
+ mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
+ }
}
public void testCreateChannelGroup() throws Exception {
@@ -88,18 +94,43 @@
new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
channel.setGroup(ncg.getId());
mNotificationManager.createNotificationChannelGroup(ncg);
+ final NotificationChannel ungrouped =
+ new NotificationChannel(mId + "!", "name", NotificationManager.IMPORTANCE_DEFAULT);
try {
mNotificationManager.createNotificationChannel(channel);
+ mNotificationManager.createNotificationChannel(ungrouped);
List<NotificationChannelGroup> ncgs =
mNotificationManager.getNotificationChannelGroups();
assertEquals(1, ncgs.size());
- assertEquals(ncg, ncgs.get(0));
+ assertEquals(ncg.getName(), ncgs.get(0).getName());
+ assertEquals(ncg.getDescription(), ncgs.get(0).getDescription());
+ assertEquals(channel.getId(), ncgs.get(0).getChannels().get(0).getId());
} finally {
mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
}
}
+ public void testGetChannelGroup() throws Exception {
+ final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
+ ncg.setDescription("bananas");
+ final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
+ final NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup(ncg.getId());
+
+ mNotificationManager.createNotificationChannelGroup(ncg);
+ mNotificationManager.createNotificationChannelGroup(ncg2);
+ mNotificationManager.createNotificationChannel(channel);
+
+ NotificationChannelGroup actual =
+ mNotificationManager.getNotificationChannelGroup(ncg.getId());
+ assertEquals(ncg.getId(), actual.getId());
+ assertEquals(ncg.getName(), actual.getName());
+ assertEquals(ncg.getDescription(), actual.getDescription());
+ assertEquals(channel.getId(), actual.getChannels().get(0).getId());
+ }
+
public void testDeleteChannelGroup() throws Exception {
final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
final NotificationChannel channel =
@@ -151,6 +182,46 @@
mNotificationManager.getNotificationChannel(mId).getImportance());
}
+ public void testCreateChannel_addToGroup() throws Exception {
+ String oldGroup = null;
+ String newGroup = "new group";
+ mNotificationManager.createNotificationChannelGroup(
+ new NotificationChannelGroup(newGroup, newGroup));
+
+ NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup(oldGroup);
+ mNotificationManager.createNotificationChannel(channel);
+
+ channel.setGroup(newGroup);
+ mNotificationManager.createNotificationChannel(channel);
+
+ final NotificationChannel updatedChannel =
+ mNotificationManager.getNotificationChannel(mId);
+ assertEquals("Failed to add non-grouped channel to a group on update ",
+ newGroup, updatedChannel.getGroup());
+ }
+
+ public void testCreateChannel_cannotChangeGroup() throws Exception {
+ String oldGroup = "old group";
+ String newGroup = "new group";
+ mNotificationManager.createNotificationChannelGroup(
+ new NotificationChannelGroup(oldGroup, oldGroup));
+ mNotificationManager.createNotificationChannelGroup(
+ new NotificationChannelGroup(newGroup, newGroup));
+
+ NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup(oldGroup);
+ mNotificationManager.createNotificationChannel(channel);
+ channel.setGroup(newGroup);
+ mNotificationManager.createNotificationChannel(channel);
+ final NotificationChannel updatedChannel =
+ mNotificationManager.getNotificationChannel(mId);
+ assertEquals("Channels should not be allowed to change groups",
+ oldGroup, updatedChannel.getGroup());
+ }
+
public void testCreateSameChannelDoesNotUpdate() throws Exception {
final NotificationChannel channel =
new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
@@ -328,6 +399,54 @@
}
}
+ public void testNotify_blockedChannel() throws Exception {
+ mNotificationManager.cancelAll();
+
+ NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_NONE);
+ mNotificationManager.createNotificationChannel(channel);
+
+ int id = 1;
+ final Notification notification =
+ new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.black)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle("notify#" + id)
+ .setContentText("This is #" + id + "notification ")
+ .build();
+ mNotificationManager.notify(id, notification);
+
+ if (!checkNotificationExistence(id, /*shouldExist=*/ false)) {
+ fail("found unexpected notification id=" + id);
+ }
+ }
+
+ public void testNotify_blockedChannelGroup() throws Exception {
+ mNotificationManager.cancelAll();
+
+ NotificationChannelGroup group = new NotificationChannelGroup(mId, "group name");
+ group.setBlocked(true);
+ mNotificationManager.createNotificationChannelGroup(group);
+ NotificationChannel channel =
+ new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setGroup(mId);
+ mNotificationManager.createNotificationChannel(channel);
+
+ int id = 1;
+ final Notification notification =
+ new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.black)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle("notify#" + id)
+ .setContentText("This is #" + id + "notification ")
+ .build();
+ mNotificationManager.notify(id, notification);
+
+ if (!checkNotificationExistence(id, /*shouldExist=*/ false)) {
+ fail("found unexpected notification id=" + id);
+ }
+ }
+
public void testCancel() throws Exception {
final int id = 9;
sendNotification(id, R.drawable.black);
@@ -683,6 +802,7 @@
found = false;
final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
for (StatusBarNotification sbn : sbns) {
+ Log.d(TAG, "Found " + sbn.getKey());
if (sbn.getId() == id) {
found = true;
break;
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 25c25e3..a69a92a 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -25,12 +25,11 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import android.os.Parcel;
import android.test.AndroidTestCase;
import android.widget.RemoteViews;
-import org.mockito.internal.matchers.Not;
-
public class NotificationTest extends AndroidTestCase {
private static final String TEXT_RESULT_KEY = "text";
private static final String DATA_RESULT_KEY = "data";
@@ -252,6 +251,68 @@
mNotification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES));
}
+ public void testMessagingStyle_isGroupConversation() {
+ mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true)
+ .setConversationTitle("test conversation title");
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .setSmallIcon(1)
+ .setContentTitle("test title")
+ .setStyle(messagingStyle)
+ .build();
+
+ assertTrue(messagingStyle.isGroupConversation());
+ assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+ }
+
+ public void testMessagingStyle_isGroupConversation_noConversationTitle() {
+ mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true)
+ .setConversationTitle(null);
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .setSmallIcon(1)
+ .setContentTitle("test title")
+ .setStyle(messagingStyle)
+ .build();
+
+ assertTrue(messagingStyle.isGroupConversation());
+ assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+ }
+
+ public void testMessagingStyle_isGroupConversation_withConversationTitle_legacy() {
+ // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+ mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+ .setGroupConversation(false)
+ .setConversationTitle("test conversation title");
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .setSmallIcon(1)
+ .setContentTitle("test title")
+ .setStyle(messagingStyle)
+ .build();
+
+ assertTrue(messagingStyle.isGroupConversation());
+ assertFalse(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+ }
+
+ public void testMessagingStyle_isGroupConversation_withoutConversationTitle_legacy() {
+ // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
+ mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true)
+ .setConversationTitle(null);
+ Notification notification = new Notification.Builder(mContext, "test id")
+ .setSmallIcon(1)
+ .setContentTitle("test title")
+ .setStyle(messagingStyle)
+ .build();
+
+ assertFalse(messagingStyle.isGroupConversation());
+ assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+ }
+
public void testToString() {
mNotification = new Notification();
assertNotNull(mNotification.toString());
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 5f8202d..d660040 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -26,6 +26,7 @@
import android.app.stubs.LocalForegroundService;
import android.app.stubs.LocalGrantedService;
import android.app.stubs.LocalService;
+import android.app.stubs.NullService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -36,6 +37,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
@@ -85,6 +87,41 @@
}
}
+ private static class NullServiceConnection implements ServiceConnection {
+ boolean mNullBinding = false;
+
+ @Override public void onServiceConnected(ComponentName name, IBinder service) {}
+ @Override public void onServiceDisconnected(ComponentName name) {}
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ synchronized (this) {
+ mNullBinding = true;
+ this.notifyAll();
+ }
+ }
+
+ public void waitForNullBinding(final long timeout) {
+ long now = SystemClock.uptimeMillis();
+ final long end = now + timeout;
+ synchronized (this) {
+ while (!mNullBinding && (now < end)) {
+ try {
+ this.wait(end - now);
+ } catch (InterruptedException e) {
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ }
+ }
+
+ public boolean nullBindingReceived() {
+ synchronized (this) {
+ return mNullBinding;
+ }
+ }
+ }
+
private class TestConnection implements ServiceConnection {
private final boolean mExpectDisconnect;
private final boolean mSetReporter;
@@ -831,4 +868,28 @@
// expected
}
}
+
+ /**
+ * Verify that when the requested service's onBind() returns null,
+ * the connection's onNullBinding() method is invoked.
+ */
+ @MediumTest
+ public void testNullServiceBinder() throws Exception {
+ Intent intent = new Intent(mContext, NullService.class);
+ intent.setAction("testNullServiceBinder");
+ NullServiceConnection conn1 = new NullServiceConnection();
+ NullServiceConnection conn2 = new NullServiceConnection();
+ try {
+ assertTrue(mContext.bindService(intent, conn1, Context.BIND_AUTO_CREATE));
+ conn1.waitForNullBinding(DELAY);
+ assertTrue(conn1.nullBindingReceived());
+
+ assertTrue(mContext.bindService(intent, conn2, Context.BIND_AUTO_CREATE));
+ conn2.waitForNullBinding(DELAY);
+ assertTrue(conn2.nullBindingReceived());
+ } finally {
+ mContext.unbindService(conn1);
+ mContext.unbindService(conn2);
+ }
+ }
}
diff --git a/tests/app/src/android/app/cts/TaskDescriptionTest.java b/tests/app/src/android/app/cts/TaskDescriptionTest.java
new file mode 100644
index 0000000..408930e
--- /dev/null
+++ b/tests/app/src/android/app/cts/TaskDescriptionTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app.cts;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.TaskDescription;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.platform.test.annotations.Presubmit;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.app.Activity;
+import android.app.stubs.MockActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+/**
+ * Build: mmma -j32 cts/tests/app
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsAppTestCases android.app.cts.TaskDescriptionTest
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class TaskDescriptionTest {
+ private static final String TEST_LABEL = "test-label";
+ private static final int TEST_NO_DATA = 0;
+ private static final int TEST_RES_DATA = 777;
+ private static final int TEST_COLOR = Color.BLACK;
+
+ @Rule
+ public ActivityTestRule<MockActivity> mTaskDescriptionActivity =
+ new ActivityTestRule<>(MockActivity.class,
+ false /* initialTouchMode */, false /* launchActivity */);
+
+ @Test
+ public void testBitmapConstructor() throws Exception {
+ final Activity activity = mTaskDescriptionActivity.launchActivity(null);
+ final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ bitmap.eraseColor(0);
+ activity.setTaskDescription(new TaskDescription(TEST_LABEL, bitmap, TEST_COLOR));
+ assertTaskDescription(activity, TEST_LABEL, TEST_NO_DATA);
+ }
+
+ @Test
+ public void testResourceConstructor() throws Exception {
+ final Activity activity = mTaskDescriptionActivity.launchActivity(null);
+ activity.setTaskDescription(new TaskDescription(TEST_LABEL, TEST_RES_DATA, TEST_COLOR));
+ assertTaskDescription(activity, TEST_LABEL, TEST_RES_DATA);
+ }
+
+ private void assertTaskDescription(Activity activity, String label, int resId) {
+ final ActivityManager am = (ActivityManager) activity.getSystemService(ACTIVITY_SERVICE);
+ List<RecentTaskInfo> recentsTasks = am.getRecentTasks(1 /* maxNum */, 0 /* flags */);
+ if (!recentsTasks.isEmpty()) {
+ final RecentTaskInfo info = recentsTasks.get(0);
+ if (activity.getTaskId() == info.id) {
+ final TaskDescription td = info.taskDescription;
+ assertNotNull(td);
+ if (resId == TEST_NO_DATA) {
+ assertNotNull(td.getIcon());
+ assertNotNull(td.getIconFilename());
+ } else {
+ assertNull(td.getIconFilename());
+ assertNull(td.getIcon());
+ }
+ assertEquals(resId, td.getIconResource());
+ assertEquals(label, td.getLabel());
+ return;
+ }
+ }
+ fail("Did not find activity (id=" + activity.getTaskId() + ") in recent tasks list");
+ }
+}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java
index f5bb5a3..5a00932 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceConnectionHandler.java
@@ -32,6 +32,7 @@
final Context mContext;
final Intent mIntent;
+ final long mDefaultWaitTime;
boolean mMonitoring;
boolean mBound;
IBinder mService;
@@ -47,8 +48,13 @@
};
public ServiceConnectionHandler(Context context, Intent intent) {
+ this(context, intent, 5*1000);
+ }
+
+ public ServiceConnectionHandler(Context context, Intent intent, long defaultWaitTime) {
mContext = context;
mIntent = intent;
+ mDefaultWaitTime = defaultWaitTime;
}
public void startMonitoring() {
@@ -64,6 +70,10 @@
}
}
+ public void waitForConnect() {
+ waitForConnect(mDefaultWaitTime);
+ }
+
public void waitForConnect(long timeout) {
final long endTime = SystemClock.uptimeMillis() + timeout;
@@ -85,6 +95,10 @@
return mService;
}
+ public void waitForDisconnect() {
+ waitForDisconnect(mDefaultWaitTime);
+ }
+
public void waitForDisconnect(long timeout) {
final long endTime = SystemClock.uptimeMillis() + timeout;
@@ -120,6 +134,10 @@
}
}
+ public void bind() {
+ bind(mDefaultWaitTime);
+ }
+
public void bind(long timeout) {
synchronized (this) {
if (mBound) {
@@ -137,6 +155,10 @@
}
}
+ public void unbind() {
+ unbind(mDefaultWaitTime);
+ }
+
public void unbind(long timeout) {
synchronized (this) {
if (!mBound) {
@@ -155,6 +177,10 @@
}
}
+ public void cleanup() {
+ cleanup(mDefaultWaitTime);
+ }
+
public void cleanup(long timeout) {
synchronized (this) {
if (mBound) {
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
index 9cad7a3..e8008df 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
@@ -42,6 +42,7 @@
final String mMyPackageName;
final Intent[] mServiceIntents;
final String mServicePackage;
+ final long mDefaultWaitTime;
final ActivityManager mAm;
final Parcel mData;
@@ -54,11 +55,18 @@
public ServiceProcessController(Context context, Instrumentation instrumentation,
String myPackageName, Intent[] serviceIntents)
throws IOException, PackageManager.NameNotFoundException {
+ this(context, instrumentation, myPackageName, serviceIntents, 5*1000);
+ }
+
+ public ServiceProcessController(Context context, Instrumentation instrumentation,
+ String myPackageName, Intent[] serviceIntents, long defaultWaitTime)
+ throws IOException, PackageManager.NameNotFoundException {
mContext = context;
mInstrumentation = instrumentation;
mMyPackageName = myPackageName;
mServiceIntents = serviceIntents;
mServicePackage = mServiceIntents[0].getComponent().getPackageName();
+ mDefaultWaitTime = defaultWaitTime;
String cmd = "pm grant " + mMyPackageName + " " + Manifest.permission.PACKAGE_USAGE_STATS;
String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
/*
@@ -72,21 +80,26 @@
mData = Parcel.obtain();
mConnections = new ServiceConnectionHandler[serviceIntents.length];
for (int i=0; i<serviceIntents.length; i++) {
- mConnections[i] = new ServiceConnectionHandler(mContext, serviceIntents[i]);
+ mConnections[i] = new ServiceConnectionHandler(mContext, serviceIntents[i],
+ mDefaultWaitTime);
}
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
mServicePackage, 0);
mUid = appInfo.uid;
- mUidForegroundListener = new UidImportanceListener(appInfo.uid);
+ mUidForegroundListener = new UidImportanceListener(appInfo.uid, mDefaultWaitTime);
mAm.addOnUidImportanceListener(mUidForegroundListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
- mUidGoneListener = new UidImportanceListener(appInfo.uid);
+ mUidGoneListener = new UidImportanceListener(appInfo.uid, mDefaultWaitTime);
mAm.addOnUidImportanceListener(mUidGoneListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY);
- mUidWatcher = new WatchUidRunner(instrumentation, appInfo.uid);
+ mUidWatcher = new WatchUidRunner(instrumentation, appInfo.uid, mDefaultWaitTime);
+ }
+
+ public void denyBackgroundOp() throws IOException {
+ denyBackgroundOp(mDefaultWaitTime);
}
public void denyBackgroundOp(long timeout) throws IOException {
@@ -123,6 +136,11 @@
String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
}
+ public void removeFromTempWhitelist() throws IOException {
+ String cmd = "cmd deviceidle tempwhitelist -r " + mServicePackage;
+ SystemUtil.runShellCommand(mInstrumentation, cmd);
+ }
+
public void cleanup() throws IOException {
removeFromWhitelist();
allowBackgroundOp();
@@ -152,6 +170,10 @@
return mUidWatcher;
}
+ public void ensureProcessGone() {
+ ensureProcessGone(mDefaultWaitTime);
+ }
+
public void ensureProcessGone(long timeout) {
for (int i=0; i<mConnections.length; i++) {
mConnections[i].bind(timeout);
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java b/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java
index 3eb402a..f015441 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/UidImportanceListener.java
@@ -25,11 +25,17 @@
*/
public final class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
final int mUid;
+ final long mDefaultWaitTime;
int mLastValue = -1;
public UidImportanceListener(int uid) {
+ this(uid, 5*1000);
+ }
+
+ public UidImportanceListener(int uid, long defaultWaitTime) {
mUid = uid;
+ mDefaultWaitTime = defaultWaitTime;
}
@Override
@@ -43,6 +49,10 @@
}
}
+ public int waitForValue(int minValue, int maxValue) {
+ return waitForValue(minValue, maxValue, mDefaultWaitTime);
+ }
+
public int waitForValue(int minValue, int maxValue, long timeout) {
final long endTime = SystemClock.uptimeMillis()+timeout;
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
index e2abf67..c077d32 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
@@ -39,6 +39,8 @@
* bit CtsAppTestCases:ActivityManagerProcessStateTest
*/
public class WatchUidRunner {
+ static final String TAG = "WatchUidRunner";
+
public static final int CMD_PROCSTATE = 0;
public static final int CMD_ACTIVE = 1;
public static final int CMD_IDLE = 2;
@@ -46,6 +48,27 @@
public static final int CMD_CACHED = 4;
public static final int CMD_GONE = 5;
+ public static final String STATE_PERSISTENT = "PER";
+ public static final String STATE_PERSISTENT_UI = "PERU";
+ public static final String STATE_TOP = "TOP";
+ public static final String STATE_BOUND_FG_SERVICE = "BFGS";
+ public static final String STATE_FG_SERVICE = "FGS";
+ public static final String STATE_TOP_SLEEPING = "TPSL";
+ public static final String STATE_IMPORTANT_FG = "IMPF";
+ public static final String STATE_IMPORTANT_BG = "IMPB";
+ public static final String STATE_TRANSIENT_BG = "TRNB";
+ public static final String STATE_BACKUP = "BKUP";
+ public static final String STATE_HEAVY_WEIGHT = "HVY";
+ public static final String STATE_SERVICE = "SVC";
+ public static final String STATE_RECEIVER = "RCVR";
+ public static final String STATE_HOME = "HOME";
+ public static final String STATE_LAST = "LAST";
+ public static final String STATE_CACHED_ACTIVITY = "CAC";
+ public static final String STATE_CACHED_ACTIVITY_CLIENT = "CACC";
+ public static final String STATE_CACHED_RECENT = "CRE";
+ public static final String STATE_CACHED_EMPTY = "CEM";
+ public static final String STATE_NONEXISTENT = "NONE";
+
static final String[] COMMAND_TO_STRING = new String[] {
"procstate", "active", "idle", "uncached", "cached", "gone"
};
@@ -53,6 +76,7 @@
final Instrumentation mInstrumentation;
final int mUid;
final String mUidStr;
+ final long mDefaultWaitTime;
final Pattern mSpaceSplitter;
final ParcelFileDescriptor mReadFd;
final FileInputStream mReadStream;
@@ -68,9 +92,14 @@
boolean mStopping;
public WatchUidRunner(Instrumentation instrumentation, int uid) {
+ this(instrumentation, uid, 5*1000);
+ }
+
+ public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime) {
mInstrumentation = instrumentation;
mUid = uid;
mUidStr = Integer.toString(uid);
+ mDefaultWaitTime = defaultWaitTime;
mSpaceSplitter = Pattern.compile("\\s+");
ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation().executeShellCommandRw(
"am watch-uids");
@@ -96,6 +125,10 @@
}
}
+ public void expect(int cmd, String procState) {
+ expect(cmd, procState, mDefaultWaitTime);
+ }
+
public void expect(int cmd, String procState, long timeout) {
long waitUntil = SystemClock.uptimeMillis() + timeout;
String[] line = waitForNextLine(waitUntil);
@@ -109,6 +142,10 @@
}
}
+ public void waitFor(int cmd, String procState) {
+ waitFor(cmd, procState, mDefaultWaitTime);
+ }
+
public void waitFor(int cmd, String procState, long timeout) {
long waitUntil = SystemClock.uptimeMillis() + timeout;
while (true) {
@@ -120,11 +157,11 @@
if (line.length >= 3 && procState.equals(line[2])) {
return;
} else {
- Log.d("XXXX", "Skipping because procstate not " + procState + ": "
+ Log.d(TAG, "Skipping because procstate not " + procState + ": "
+ Arrays.toString(line));
}
} else {
- Log.d("XXXX", "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
+ Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
+ Arrays.toString(line));
}
}
@@ -170,14 +207,14 @@
try {
while ((line = readNextLine()) != null) {
if (line.length < 2) {
- Log.d("XXXXX", "Skipping: " + mLastReadLine);
+ Log.d(TAG, "Skipping too short: " + mLastReadLine);
continue;
}
if (!line[0].equals(mUidStr)) {
- Log.d("XXXXX", "Skipping: " + mLastReadLine);
+ Log.d(TAG, "Skipping ignored uid: " + mLastReadLine);
continue;
}
- Log.d("XXXXX", "Enqueueing: " + mLastReadLine);
+ Log.d(TAG, "Enqueueing: " + mLastReadLine);
synchronized (mPendingLines) {
if (mStopping) {
return;
@@ -187,7 +224,7 @@
}
}
} catch (IOException e) {
- Log.w("WatchUidRunner", "Failed reading", e);
+ Log.w(TAG, "Failed reading", e);
}
}
diff --git a/tests/aslr/AndroidTest.xml b/tests/aslr/AndroidTest.xml
index 8b9c893..87c5dfb 100644
--- a/tests/aslr/AndroidTest.xml
+++ b/tests/aslr/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Aslr Malloc test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index b8ab1f9..0f1402a 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -32,6 +32,7 @@
</intent-filter>
</activity>
<activity android:name=".PreFilledLoginActivity" />
+ <activity android:name=".LoginWithStringsActivity" />
<activity android:name=".WelcomeActivity"/>
<activity android:name=".ViewAttributesTestActivity" />
<activity android:name=".AuthenticationActivity" />
@@ -66,6 +67,14 @@
<activity android:name=".WebViewActivity"/>
<activity android:name=".TrampolineWelcomeActivity"/>
<activity android:name=".AttachedContextActivity"/>
+ <activity android:name=".DialogLauncherActivity" >
+ <intent-filter>
+ <!-- This intent filter is not really needed by CTS, but it maks easier to launch
+ this app during CTS development... -->
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
<service
android:name=".InstrumentedAutoFillService"
@@ -83,6 +92,15 @@
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
</service>
+ <!-- BadAutofillService does not declare the proper permission -->
+ <service
+ android:name=".BadAutofillService"
+ android:label="BadAutofillService"
+ android:permission="android.permission.BIND_AUTOFILL" >
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillService" />
+ </intent-filter>
+ </service>
</application>
<instrumentation
diff --git a/tests/autofillservice/res/layout/dialog_launcher_activity.xml b/tests/autofillservice/res/layout/dialog_launcher_activity.xml
new file mode 100644
index 0000000..62d6d9b
--- /dev/null
+++ b/tests/autofillservice/res/layout/dialog_launcher_activity.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:importantForAutofill="noExcludeDescendants"
+ android:orientation="vertical" >
+
+ <Button
+ android:id="@+id/launch_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Launch" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/login_activity.xml b/tests/autofillservice/res/layout/login_activity.xml
index e16d1c4..2a971b1 100644
--- a/tests/autofillservice/res/layout/login_activity.xml
+++ b/tests/autofillservice/res/layout/login_activity.xml
@@ -37,6 +37,9 @@
<EditText
android:id="@+id/username"
+ android:minEms="2"
+ android:maxEms="5"
+ android:maxLength="25"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
diff --git a/tests/autofillservice/res/layout/login_with_strings_activity.xml b/tests/autofillservice/res/layout/login_with_strings_activity.xml
new file mode 100644
index 0000000..2f90761
--- /dev/null
+++ b/tests/autofillservice/res/layout/login_with_strings_activity.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/username_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/username_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@+string/username_string" />
+
+ <EditText
+ android:id="@+id/username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@+string/password_string" />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/clear"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Clear" />
+
+ <Button
+ android:id="@+id/save"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Save" />
+
+ <Button
+ android:id="@+id/login"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Login" />
+
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Cancel" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/output"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/third_line_only.xml b/tests/autofillservice/res/layout/third_line_only.xml
new file mode 100644
index 0000000..0267c94
--- /dev/null
+++ b/tests/autofillservice/res/layout/third_line_only.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/third"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
diff --git a/tests/autofillservice/res/layout/three_horizontal_text_fields_last_two_invisible.xml b/tests/autofillservice/res/layout/three_horizontal_text_fields_last_two_invisible.xml
new file mode 100644
index 0000000..3a96389
--- /dev/null
+++ b/tests/autofillservice/res/layout/three_horizontal_text_fields_last_two_invisible.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/static_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="YO:"/>
+
+ <TextView android:id="@+id/first"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
+
+ <TextView android:id="@+id/second"
+ android:text="2ND, Y U NO HIDDEN?"
+ android:visibility="invisible"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
+
+ <TextView android:id="@+id/third"
+ android:text="3RD, Y U NO HIDDEN?"
+ android:visibility="invisible"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
+
+ <ImageView android:id="@+id/img"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/two_horizontal_text_fields.xml b/tests/autofillservice/res/layout/two_horizontal_text_fields.xml
index 944f926..773afae 100644
--- a/tests/autofillservice/res/layout/two_horizontal_text_fields.xml
+++ b/tests/autofillservice/res/layout/two_horizontal_text_fields.xml
@@ -16,6 +16,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parent"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
diff --git a/tests/autofillservice/res/layout/webview_activity.xml b/tests/autofillservice/res/layout/webview_activity.xml
index a975186..8740273 100644
--- a/tests/autofillservice/res/layout/webview_activity.xml
+++ b/tests/autofillservice/res/layout/webview_activity.xml
@@ -14,8 +14,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
-->
-<WebView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/webview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
-/>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/outsideContainer1"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Outside 1" />
+
+ <EditText
+ android:id="@+id/outside1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/outsideContainer2"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Outside 2" />
+
+ <EditText
+ android:id="@+id/outside2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <android.autofillservice.cts.MyWebView
+ android:id="@+id/webview"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ />
+
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/welcome_activity.xml b/tests/autofillservice/res/layout/welcome_activity.xml
index 292aacd..a79752f 100644
--- a/tests/autofillservice/res/layout/welcome_activity.xml
+++ b/tests/autofillservice/res/layout/welcome_activity.xml
@@ -22,7 +22,7 @@
android:orientation="vertical" >
<TextView
- android:id="@+id/output"
+ android:id="@+id/welcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to the jungle!" />
diff --git a/tests/autofillservice/res/values/strings.xml b/tests/autofillservice/res/values/strings.xml
index 8720a71..785c7a5 100644
--- a/tests/autofillservice/res/values/strings.xml
+++ b/tests/autofillservice/res/values/strings.xml
@@ -26,4 +26,7 @@
<item>never</item>
</string-array>
+ <string name="username_string">Username</string>
+ <string name="password_string">Password</string>
+
</resources>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
index efc0b2c..1de07cc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
@@ -35,14 +35,14 @@
* Run an action in the UI thread, and blocks caller until the action is finished.
*/
public final void syncRunOnUiThread(Runnable action) {
- syncRunOnUiThread(action, Helper.UI_TIMEOUT_MS);
+ syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms());
}
/**
* Run an action in the UI thread, and blocks caller until the action is finished or it times
* out.
*/
- public final void syncRunOnUiThread(Runnable action, int timeoutMs) {
+ public final void syncRunOnUiThread(Runnable action, long timeoutMs) {
final CountDownLatch latch = new CountDownLatch(1);
runOnUiThread(() -> {
action.run();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
new file mode 100644
index 0000000..af713d3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import java.util.regex.Pattern;
+
+/**
+ * A {@link TextWatcher} that appends pound signs ({@code #} at the beginning and end of the text.
+ */
+public final class AntiTrimmerTextWatcher implements TextWatcher {
+
+ /**
+ * Regex used to revert a String that was "anti-trimmed".
+ */
+ public static final Pattern TRIMMER_PATTERN = Pattern.compile("#(.*)#");
+
+ private final EditText mView;
+
+ public AntiTrimmerTextWatcher(EditText view) {
+ mView = view;
+ mView.addTextChangedListener(this);
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mView.removeTextChangedListener(this);
+ mView.setText("#" + s + "#");
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
index 45f7264..50526dd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
@@ -56,7 +56,7 @@
sReplier.getNextFillRequest();
// Select dataset
- sUiBot.selectDataset("fill me");
+ mUiBot.selectDataset("fill me");
// Assert results
fillExpectation.assertAutoFilled();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
index 570de4e..62072c3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
@@ -50,6 +50,13 @@
private static final String EXTRA_DATASET_ID = "dataset_id";
private static final String EXTRA_RESPONSE_ID = "response_id";
+ /**
+ * When launched with this intent, it will pass it back to the
+ * {@link AutofillManager#EXTRA_CLIENT_STATE} of the result.
+ */
+ private static final String EXTRA_OUTPUT_CLIENT_STATE = "output_client_state";
+
+
private static final int MSG_WAIT_FOR_LATCH = 1;
private static Bundle sData;
@@ -84,10 +91,15 @@
*/
public static IntentSender createSender(Context context, int id,
CannedDataset dataset) {
+ return createSender(context, id, dataset, null);
+ }
+
+ public static IntentSender createSender(Context context, int id,
+ CannedDataset dataset, Bundle outClientState) {
Preconditions.checkArgument(id > 0, "id must be positive");
Preconditions.checkState(sDatasets.get(id) == null, "already have id");
sDatasets.put(id, dataset);
- return createSender(context, EXTRA_DATASET_ID, id);
+ return createSender(context, EXTRA_DATASET_ID, id, outClientState);
}
/**
@@ -95,15 +107,25 @@
*/
public static IntentSender createSender(Context context, int id,
CannedFillResponse response) {
+ return createSender(context, id, response, null);
+ }
+
+ public static IntentSender createSender(Context context, int id,
+ CannedFillResponse response, Bundle outData) {
Preconditions.checkArgument(id > 0, "id must be positive");
Preconditions.checkState(sResponses.get(id) == null, "already have id");
sResponses.put(id, response);
- return createSender(context, EXTRA_RESPONSE_ID, id);
+ return createSender(context, EXTRA_RESPONSE_ID, id, outData);
}
- private static IntentSender createSender(Context context, String extraName, int id) {
+ private static IntentSender createSender(Context context, String extraName, int id,
+ Bundle outClientState) {
final Intent intent = new Intent(context, AuthenticationActivity.class);
intent.putExtra(extraName, id);
+ if (outClientState != null) {
+ Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
+ intent.putExtra(EXTRA_OUTPUT_CLIENT_STATE, outClientState);
+ }
final PendingIntent pendingIntent = PendingIntent.getActivity(context, id, intent, 0);
sPendingIntents.add(pendingIntent);
return pendingIntent.getIntentSender();
@@ -213,6 +235,13 @@
// Pass on the auth result
final Intent intent = new Intent();
intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
+
+ final Bundle outClientState = getIntent().getBundleExtra(EXTRA_OUTPUT_CLIENT_STATE);
+ if (outClientState != null) {
+ Log.d(TAG, "Adding " + outClientState + " as " + AutofillManager.EXTRA_CLIENT_STATE);
+ intent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, outClientState);
+ }
+
final int resultCode;
synchronized (sLock) {
resultCode = sResultCode;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 74e168b..a8d73c5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -19,39 +19,60 @@
import static android.autofillservice.cts.Helper.getContext;
import static android.autofillservice.cts.Helper.getLoggingLevel;
import static android.autofillservice.cts.Helper.hasAutofillFeature;
-import static android.autofillservice.cts.Helper.runShellCommand;
import static android.autofillservice.cts.Helper.setLoggingLevel;
import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
-import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
+import android.autofillservice.cts.common.SettingsStateKeeperRule;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.widget.RemoteViews;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
import org.junit.runner.RunWith;
-import java.util.List;
-
/**
* Base class for all other tests.
*/
@RunWith(AndroidJUnit4.class)
-abstract class AutoFillServiceTestCase {
+// NOTE: @ClassRule requires it to be public
+public abstract class AutoFillServiceTestCase {
private static final String TAG = "AutoFillServiceTestCase";
- protected static UiBot sUiBot;
+ static final UiBot sDefaultUiBot = new UiBot();
protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+ @ClassRule
+ public static final SettingsStateKeeperRule mServiceSettingsKeeper =
+ new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE);
+
+ @Rule
+ public final TestWatcher watcher = new TestWatcher() {
+ @Override
+ protected void starting(Description description) {
+ JUnitHelper.setCurrentTestName(description.getDisplayName());
+ }
+
+ @Override
+ protected void finished(Description description) {
+ JUnitHelper.setCurrentTestName(null);
+ }
+ };
+
@Rule
public final RetryRule mRetryRule = new RetryRule(2);
@@ -62,16 +83,28 @@
public final RequiredFeatureRule mRequiredFeatureRule =
new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
- protected final Context mContext;
+ @Rule
+ public final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+ .run(() -> sReplier.assertNumberUnhandledFillRequests(0))
+ .run(() -> sReplier.assertNumberUnhandledSaveRequests(0))
+ .add(() -> { return sReplier.getExceptions(); });
+
+ protected final Context mContext = sContext;
protected final String mPackageName;
+ protected final UiBot mUiBot;
+
/**
* Stores the previous logging level so it's restored after the test.
*/
private String mLoggingLevel;
protected AutoFillServiceTestCase() {
- mContext = InstrumentationRegistry.getTargetContext();
+ this(sDefaultUiBot);
+ }
+
+ protected AutoFillServiceTestCase(UiBot uiBot) {
mPackageName = mContext.getPackageName();
+ mUiBot = uiBot;
}
@BeforeClass
@@ -85,22 +118,6 @@
runShellCommand("cmd statusbar collapse");
}
- @BeforeClass
- public static void setUiBot() throws Exception {
- if (!hasAutofillFeature()) return;
-
- sUiBot = new UiBot(InstrumentationRegistry.getInstrumentation());
- }
-
- @AfterClass
- public static void resetSettings() {
- if (!hasAutofillFeature()) return;
-
- // Clean up only - no need to call disableService() because it doesn't need to fail if
- // it's not reset.
- runShellCommand("settings delete secure %s", AUTOFILL_SERVICE);
- }
-
@Before
public void reset() {
sReplier.reset();
@@ -121,6 +138,15 @@
}
}
+ /**
+ * Cleans up activities that might have been left over.
+ */
+ @Before
+ @After
+ public void finishActivities() {
+ WelcomeActivity.finishIt(mUiBot);
+ }
+
@After
public void resetVerboseLogging() {
try {
@@ -130,24 +156,6 @@
}
}
- // TODO: we shouldn't throw exceptions on @After / @AfterClass because if the test failed, these
- // exceptions would mask the real cause. A better approach might be using a @Rule or some other
- // visitor pattern.
- @After
- public void assertNothingIsPending() throws Throwable {
- final MultipleExceptionsCatcher catcher = new MultipleExceptionsCatcher()
- .run(() -> sReplier.assertNumberUnhandledFillRequests(0))
- .run(() -> sReplier.assertNumberUnhandledSaveRequests(0));
-
- final List<Exception> replierExceptions = sReplier.getExceptions();
- if (replierExceptions != null) {
- for (Exception e : replierExceptions) {
- catcher.add(e);
- }
- }
- catcher.throwIfAny();
- }
-
@After
public void ignoreFurtherRequests() {
InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
index b966078..43bc24f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -36,6 +36,8 @@
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* Tests that the session finishes when the views and fragments go away
*/
@@ -62,13 +64,12 @@
// firstRemove and secondRemove run in the UI Thread; firstCheck doesn't
private void removeViewsBaseTest(@NonNull Runnable firstRemove, @Nullable Runnable firstCheck,
- @Nullable Runnable secondRemove, String... viewsToSave)
- throws Exception {
+ @Nullable Runnable secondRemove, String... viewsToSave) throws Exception {
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
- .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, viewsToSave).build());
// Trigger autofill
@@ -79,7 +80,7 @@
sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// remove first set of views
mActivity.syncRunOnUiThread(() -> {
@@ -99,7 +100,7 @@
}
// Save should be shows after all remove operations were executed
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
SaveRequest saveRequest = sReplier.getNextSaveRequest();
for (String view : viewsToSave) {
@@ -110,11 +111,24 @@
@Test
public void removeBothViewsToFinishSession() throws Exception {
+ final AtomicReference<Exception> ref = new AtomicReference<>();
removeViewsBaseTest(
() -> ((ViewGroup) mEditText1.getParent()).removeView(mEditText1),
- () -> sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC),
+ () -> assertSaveNotShowing(ref),
() -> ((ViewGroup) mEditText2.getParent()).removeView(mEditText2),
"editText1", "editText2");
+ final Exception e = ref.get();
+ if (e != null) {
+ throw e;
+ }
+ }
+
+ private void assertSaveNotShowing(AtomicReference<Exception> ref) {
+ try {
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ } catch (Exception e) {
+ ref.set(e);
+ }
}
@Test
@@ -185,7 +199,7 @@
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
- .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText1").build());
// Trigger autofill
@@ -196,7 +210,7 @@
sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
mActivity.syncRunOnUiThread(() -> {
mEditText1.setText("editText1-filled");
@@ -212,20 +226,20 @@
mActivity.syncRunOnUiThread(removeInBackGround);
}
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
// Remove previously started activity from top
- sUiBot.selectById("android.autofillservice.cts:id/button");
+ mUiBot.selectById("android.autofillservice.cts:id/button");
mActivity.waitUntilResumed();
if (removeInForeGroup != null) {
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
mActivity.syncRunOnUiThread(removeInForeGroup);
}
// Save should be shows after all remove operations were executed
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertThat(findNodeByResourceId(saveRequest.structure, "editText1")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
index 92a19a3..9c44f8f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
@@ -16,13 +16,14 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import android.util.Log;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
+
/**
* Custom JUnit4 rule that improves autofill-related logging by:
*
@@ -52,8 +53,12 @@
try {
base.evaluate();
} catch (Throwable t) {
- final String dump = runShellCommand("dumpsys autofill");
- Log.e(mTag, "dump for " + description.getDisplayName() + ": \n" + dump, t);
+ final String name = description.getDisplayName();
+ final String autofillDump = runShellCommand("dumpsys autofill");
+ Log.e(mTag, "autofill dump for " + name + ": \n" + autofillDump, t);
+ final String activityDump =
+ runShellCommand("dumpsys activity android.autofillservice.cts");
+ Log.e(mTag, "activity dump for " + name + ": \n" + activityDump, t);
throw t;
} finally {
if (!levelBefore.equals("verbose")) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
index c319730..9fcf334 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
@@ -188,7 +188,7 @@
startAutoFill(mEditText);
// Autofill it.
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
if (expectAutoFill) {
// Check the results.
@@ -241,7 +241,7 @@
startAutoFill(mCompoundButton);
// Autofill it.
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
if (expectAutoFill) {
// Check the results.
@@ -294,7 +294,7 @@
startAutoFill(mSpinner);
// Autofill it.
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
if (expectAutoFill) {
// Check the results.
@@ -361,7 +361,7 @@
startAutoFill(mEditText);
// Autofill it.
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
if (expectAutoFill) {
// Check the results.
@@ -430,7 +430,7 @@
startAutoFill(mEditText);
// Autofill it.
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
if (expectAutoFill) {
// Check the results.
@@ -486,7 +486,7 @@
startAutoFill(mEditText);
// Autofill it.
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
if (expectAutoFill) {
// Check the results.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
new file mode 100644
index 0000000..0f862f5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * An {@link AutofillService} implementation that does fails if called upon.
+ */
+public class BadAutofillService extends AutofillService {
+
+ private static final String TAG = "BadAutofillService";
+
+ static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
+ + "/." + BadAutofillService.class.getSimpleName();
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ Log.e(TAG, "onFillRequest() should never be called");
+ throw new UnsupportedOperationException("onFillRequest() should never be called");
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.e(TAG, "onSaveRequest() should never be called");
+ throw new UnsupportedOperationException("onSaveRequest() should never be called");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java b/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
new file mode 100644
index 0000000..f17f7dc
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.Transformation;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BatchUpdatesTest {
+
+ private final BatchUpdates.Builder mBuilder = new BatchUpdates.Builder();
+
+ @Test
+ public void testAddTransformation_null() {
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.transformChild(42, null));
+ }
+
+ @Test
+ public void testAddTransformation_invalidClass() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.transformChild(42, mock(Transformation.class)));
+ }
+
+ @Test
+ public void testSetUpdateTemplate_null() {
+ assertThrows(NullPointerException.class, () -> mBuilder.updateTemplate(null));
+ }
+
+ @Test
+ public void testEmptyObject() {
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+ }
+
+ @Test
+ public void testNoMoreChangesAfterBuild() {
+ assertThat(mBuilder.updateTemplate(mock(RemoteViews.class)).build()).isNotNull();
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.updateTemplate(mock(RemoteViews.class)));
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.transformChild(42, mock(InternalTransformation.class)));
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index cf2cc6d..e51f957 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -27,8 +27,11 @@
import android.service.autofill.Dataset;
import android.service.autofill.FillCallback;
import android.service.autofill.FillResponse;
+import android.service.autofill.Sanitizer;
import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
import android.service.autofill.Validator;
+import android.util.Pair;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
@@ -39,6 +42,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Function;
+import java.util.regex.Pattern;
/**
* Helper class used to produce a {@link FillResponse} based on expected fields that should be
@@ -59,6 +63,7 @@
private final ResponseType mResponseType;
private final List<CannedDataset> mDatasets;
+ private final ArrayList<Pair<Sanitizer, AutofillId[]>> mSanitizers;
private final String mFailureMessage;
private final int mSaveType;
private final Validator mValidator;
@@ -68,12 +73,19 @@
private final CustomDescription mCustomDescription;
private final Bundle mExtras;
private final RemoteViews mPresentation;
+ private final RemoteViews mHeader;
+ private final RemoteViews mFooter;
private final IntentSender mAuthentication;
private final String[] mAuthenticationIds;
private final String[] mIgnoredIds;
private final int mNegativeActionStyle;
private final IntentSender mNegativeActionListener;
- private final int mFlags;
+ private final int mSaveInfoFlags;
+ private final int mFillResponseFlags;
+ private final AutofillId mSaveTriggerId;
+ private final long mDisableDuration;
+ private final AutofillId[] mFieldClassificationIds;
+ private final boolean mFieldClassificationIdsOverflow;
private CannedFillResponse(Builder builder) {
mResponseType = builder.mResponseType;
@@ -87,12 +99,20 @@
mSaveType = builder.mSaveType;
mExtras = builder.mExtras;
mPresentation = builder.mPresentation;
+ mHeader = builder.mHeader;
+ mFooter = builder.mFooter;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
mNegativeActionStyle = builder.mNegativeActionStyle;
mNegativeActionListener = builder.mNegativeActionListener;
- mFlags = builder.mFlags;
+ mSanitizers = builder.mSanitizers;
+ mSaveInfoFlags = builder.mSaveInfoFlags;
+ mFillResponseFlags = builder.mFillResponseFlags;
+ mSaveTriggerId = builder.mSaveTriggerId;
+ mDisableDuration = builder.mDisableDuration;
+ mFieldClassificationIds = builder.mFieldClassificationIds;
+ mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
}
/**
@@ -122,7 +142,8 @@
* structure.
*/
FillResponse asFillResponse(Function<String, ViewNode> nodeResolver) {
- final FillResponse.Builder builder = new FillResponse.Builder();
+ final FillResponse.Builder builder = new FillResponse.Builder()
+ .setFlags(mFillResponseFlags);
if (mDatasets != null) {
for (CannedDataset cannedDataset : mDatasets) {
final Dataset dataset = cannedDataset.asDataset(nodeResolver);
@@ -137,7 +158,7 @@
: new SaveInfo.Builder(mSaveType,
getAutofillIds(nodeResolver, mRequiredSavableIds));
- saveInfo.setFlags(mFlags);
+ saveInfo.setFlags(mSaveInfoFlags);
if (mValidator != null) {
saveInfo.setValidator(mValidator);
@@ -153,6 +174,14 @@
if (mCustomDescription != null) {
saveInfo.setCustomDescription(mCustomDescription);
}
+
+ for (Pair<Sanitizer, AutofillId[]> sanitizer : mSanitizers) {
+ saveInfo.addSanitizer(sanitizer.first, sanitizer.second);
+ }
+
+ if (mSaveTriggerId != null) {
+ saveInfo.setTriggerId(mSaveTriggerId);
+ }
builder.setSaveInfo(saveInfo.build());
}
if (mIgnoredIds != null) {
@@ -162,9 +191,29 @@
builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
mAuthentication, mPresentation);
}
- return builder
- .setClientState(mExtras)
- .build();
+ if (mDisableDuration > 0) {
+ builder.disableAutofill(mDisableDuration);
+ }
+ if (mFieldClassificationIdsOverflow) {
+ final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
+ final AutofillId[] fieldIds = new AutofillId[length];
+ for (int i = 0; i < length; i++) {
+ fieldIds[i] = new AutofillId(i);
+ }
+ builder.setFieldClassificationIds(fieldIds);
+ } else if (mFieldClassificationIds != null) {
+ builder.setFieldClassificationIds(mFieldClassificationIds);
+ }
+ if (mExtras != null) {
+ builder.setClientState(mExtras);
+ }
+ if (mHeader != null) {
+ builder.setHeader(mHeader);
+ }
+ if (mFooter != null) {
+ builder.setFooter(mFooter);
+ }
+ return builder.build();
}
@Override
@@ -173,14 +222,22 @@
+ ",datasets=" + mDatasets
+ ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
+ ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
- + ", flags=" + mFlags
+ + ", saveInfoFlags=" + mSaveInfoFlags
+ + ", fillResponseFlags=" + mFillResponseFlags
+ ", failureMessage=" + mFailureMessage
+ ", saveDescription=" + mSaveDescription
+ ", mCustomDescription=" + mCustomDescription
+ ", hasPresentation=" + (mPresentation != null)
+ + ", hasHeader=" + (mHeader != null)
+ + ", hasFooter=" + (mFooter != null)
+ ", hasAuthentication=" + (mAuthentication != null)
+ ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
+ ", ignoredIds=" + Arrays.toString(mIgnoredIds)
+ + ", sanitizers =" + mSanitizers
+ + ", saveTriggerId=" + mSaveTriggerId
+ + ", disableDuration=" + mDisableDuration
+ + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
+ + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
+ "]";
}
@@ -192,6 +249,7 @@
static class Builder {
private final List<CannedDataset> mDatasets = new ArrayList<>();
+ private final ArrayList<Pair<Sanitizer, AutofillId[]>> mSanitizers = new ArrayList<>();
private final ResponseType mResponseType;
private String mFailureMessage;
private Validator mValidator;
@@ -202,12 +260,19 @@
public int mSaveType = -1;
private Bundle mExtras;
private RemoteViews mPresentation;
+ private RemoteViews mFooter;
+ private RemoteViews mHeader;
private IntentSender mAuthentication;
private String[] mAuthenticationIds;
private String[] mIgnoredIds;
private int mNegativeActionStyle;
private IntentSender mNegativeActionListener;
- private int mFlags;
+ private int mSaveInfoFlags;
+ private int mFillResponseFlags;
+ private AutofillId mSaveTriggerId;
+ private long mDisableDuration;
+ private AutofillId[] mFieldClassificationIds;
+ private boolean mFieldClassificationIdsOverflow;
public Builder(ResponseType type) {
mResponseType = type;
@@ -240,8 +305,13 @@
return this;
}
- public Builder setFlags(int flags) {
- mFlags = flags;
+ public Builder setSaveInfoFlags(int flags) {
+ mSaveInfoFlags = flags;
+ return this;
+ }
+
+ public Builder setFillResponseFlags(int flags) {
+ mFillResponseFlags = flags;
return this;
}
@@ -312,6 +382,14 @@
return this;
}
+ /**
+ * Adds a save sanitizer.
+ */
+ public Builder addSanitizer(Sanitizer sanitizer, AutofillId... ids) {
+ mSanitizers.add(new Pair<>(sanitizer, ids));
+ return this;
+ }
+
public CannedFillResponse build() {
return new CannedFillResponse(this);
}
@@ -324,6 +402,50 @@
mFailureMessage = message;
return this;
}
+
+ /**
+ * Sets the view that explicitly triggers save.
+ */
+ public Builder setSaveTriggerId(AutofillId id) {
+ assertWithMessage("already set").that(mSaveTriggerId).isNull();
+ mSaveTriggerId = id;
+ return this;
+ }
+
+ public Builder disableAutofill(long duration) {
+ assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
+ mDisableDuration = duration;
+ return this;
+ }
+
+ /**
+ * Sets the ids used for field classification.
+ */
+ public Builder setFieldClassificationIds(AutofillId... ids) {
+ assertWithMessage("already set").that(mFieldClassificationIds).isNull();
+ mFieldClassificationIds = ids;
+ return this;
+ }
+
+ /**
+ * Forces the service to throw an exception when setting the fields classification ids.
+ */
+ public Builder setFieldClassificationIdsOverflow() {
+ mFieldClassificationIdsOverflow = true;
+ return this;
+ }
+
+ public Builder setHeader(RemoteViews header) {
+ assertWithMessage("already set").that(mHeader).isNull();
+ mHeader = header;
+ return this;
+ }
+
+ public Builder setFooter(RemoteViews footer) {
+ assertWithMessage("already set").that(mFooter).isNull();
+ mFooter = footer;
+ return this;
+ }
}
/**
@@ -344,6 +466,7 @@
static class CannedDataset {
private final Map<String, AutofillValue> mFieldValues;
private final Map<String, RemoteViews> mFieldPresentations;
+ private final Map<String, Pattern> mFieldFilters;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
private final String mId;
@@ -351,6 +474,7 @@
private CannedDataset(Builder builder) {
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
+ mFieldFilters = builder.mFieldFilters;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mId = builder.mId;
@@ -374,10 +498,19 @@
final AutofillId autofillid = node.getAutofillId();
final AutofillValue value = entry.getValue();
final RemoteViews presentation = mFieldPresentations.get(id);
+ final Pattern filter = mFieldFilters.get(id);
if (presentation != null) {
- builder.setValue(autofillid, value, presentation);
+ if (filter == null) {
+ builder.setValue(autofillid, value, presentation);
+ } else {
+ builder.setValue(autofillid, value, filter, presentation);
+ }
} else {
- builder.setValue(autofillid, value);
+ if (filter == null) {
+ builder.setValue(autofillid, value);
+ } else {
+ builder.setValue(autofillid, value, filter);
+ }
}
}
}
@@ -390,12 +523,15 @@
return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
+ ", fieldPresentations=" + (mFieldPresentations)
+ ", hasAuthentication=" + (mAuthentication != null)
- + ", fieldValues=" + mFieldValues + "]";
+ + ", fieldValues=" + mFieldValues
+ + ", fieldFilters=" + mFieldFilters + "]";
}
static class Builder {
private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
+ private final Map<String, Pattern> mFieldFilters = new HashMap<>();
+
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private String mId;
@@ -420,6 +556,17 @@
}
/**
+ * Sets the canned value of a text field based on its {@code id}.
+ *
+ * <p>The meaning of the id is defined by the object using the canned dataset.
+ * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+ * {@link IdMode}.
+ */
+ public Builder setField(String id, String text, Pattern filter) {
+ return setField(id, AutofillValue.forText(text), filter);
+ }
+
+ /**
* Sets the canned value of a list field based on its its {@code id}.
*
* <p>The meaning of the id is defined by the object using the canned dataset.
@@ -465,6 +612,19 @@
}
/**
+ * Sets the canned value of a date field based on its {@code id}.
+ *
+ * <p>The meaning of the id is defined by the object using the canned dataset.
+ * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+ * {@link IdMode}.
+ */
+ public Builder setField(String id, AutofillValue value, Pattern filter) {
+ setField(id, value);
+ mFieldFilters.put(id, filter);
+ return this;
+ }
+
+ /**
* Sets the canned value of a field based on its {@code id}.
*
* <p>The meaning of the id is defined by the object using the canned dataset.
@@ -478,6 +638,20 @@
}
/**
+ * Sets the canned value of a field based on its {@code id}.
+ *
+ * <p>The meaning of the id is defined by the object using the canned dataset.
+ * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+ * {@link IdMode}.
+ */
+ public Builder setField(String id, String text, RemoteViews presentation,
+ Pattern filter) {
+ setField(id, text, presentation);
+ mFieldFilters.put(id, filter);
+ return this;
+ }
+
+ /**
* Sets the view to present the response in the UI.
*/
public Builder setPresentation(RemoteViews presentation) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
index 84be355..141b8d0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
@@ -45,6 +46,7 @@
* </ul>
*/
public class CheckoutActivity extends AbstractAutoFillActivity {
+ private static final String TAG = "CheckoutActivity";
private static final long BUY_TIMEOUT_MS = 1000;
static final String ID_CC_NUMBER = "cc_number";
@@ -104,9 +106,18 @@
mClearButton.setOnClickListener((v) -> resetFields());
}
- static void finishIt() {
+ @Override
+ public void finish() {
+ super.finish();
+
+ sInstance = null;
+ }
+
+ static void finishIt(UiBot uiBot) {
if (sInstance != null) {
+ Log.d(TAG, "So long and thanks for all the fish!");
sInstance.finish();
+ uiBot.assertGoneByRelativeId(ID_CC_NUMBER, Timeouts.ACTIVITY_RESURRECTION);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 59b0b7c..a3ce455 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -44,13 +44,13 @@
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.service.autofill.CharSequenceTransformation;
import android.service.autofill.CustomDescription;
+import android.service.autofill.ImageTransformation;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.widget.ArrayAdapter;
import android.widget.RemoteViews;
import android.widget.Spinner;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -74,11 +74,6 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
@Test
public void testAutofill() throws Exception {
// Set service.
@@ -112,7 +107,7 @@
.inOrder();
// Auto-fill it.
- sUiBot.selectDataset("ACME CC");
+ mUiBot.selectDataset("ACME CC");
// Check the results.
mActivity.assertAutoFilled();
@@ -152,7 +147,7 @@
assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNull();
// Auto-fill it.
- sUiBot.selectDataset("ACME CC");
+ mUiBot.selectDataset("ACME CC");
// Check the results.
mActivity.assertAutoFilled();
@@ -195,7 +190,7 @@
.containsExactly("never", "today", "tomorrow", "yesterday").inOrder();
// Auto-fill it.
- sUiBot.selectDataset("ACME CC");
+ mUiBot.selectDataset("ACME CC");
// Check the results.
mActivity.assertAutoFilled();
@@ -235,7 +230,7 @@
mActivity.onAddress((v) -> v.check(R.id.work_address));
mActivity.onSaveCc((v) -> v.setChecked(false));
mActivity.tapBuy();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
// Assert sanitization on save: everything should be available!
@@ -249,11 +244,20 @@
assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_SAVE_CC), false);
}
+ @Test
+ public void testCustomizedSaveUi() throws Exception {
+ customizedSaveUi(false);
+ }
+
+ @Test
+ public void testCustomizedSaveUiWithContentDescription() throws Exception {
+ customizedSaveUi(true);
+ }
+
/**
* Tests that a spinner can be used on custom save descriptions.
*/
- @Test
- public void testCustomizedSaveUi() throws Exception {
+ private void customizedSaveUi(boolean withContentDescription) throws Exception {
// Set service.
enableService();
@@ -268,9 +272,18 @@
final CharSequenceTransformation trans2 = new CharSequenceTransformation
.Builder(mActivity.getCcExpiration().getAutofillId(), Pattern.compile("(.*)"), "$1")
.build();
+ final ImageTransformation trans3 = (withContentDescription
+ ? new ImageTransformation.Builder(mActivity.getCcNumber().getAutofillId(),
+ Pattern.compile("(.*)"), R.drawable.android,
+ "One image is worth thousand words")
+ : new ImageTransformation.Builder(mActivity.getCcNumber().getAutofillId(),
+ Pattern.compile("(.*)"), R.drawable.android))
+ .build();
+
final CustomDescription customDescription = new CustomDescription.Builder(presentation)
.addChild(R.id.first, trans1)
.addChild(R.id.second, trans2)
+ .addChild(R.id.img, trans3)
.build();
sReplier.addResponse(new CannedFillResponse.Builder()
@@ -291,10 +304,10 @@
mActivity.tapBuy();
// First make sure the UI is shown...
- final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+ final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
// Then make sure it does have the custom views on it...
- final UiObject2 staticText = saveUi.findObject(By.res(packageName, "static_text"));
+ final UiObject2 staticText = saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT));
assertThat(staticText).isNotNull();
assertThat(staticText.getText()).isEqualTo("YO:");
@@ -305,6 +318,15 @@
final UiObject2 expiration = saveUi.findObject(By.res(packageName, "second"));
assertThat(expiration).isNotNull();
assertThat(expiration.getText()).isEqualTo("today");
+
+ final UiObject2 image = saveUi.findObject(By.res(packageName, "img"));
+ assertThat(image).isNotNull();
+ final String contentDescription = image.getContentDescription();
+ if (withContentDescription) {
+ assertThat(contentDescription).isEqualTo("One image is worth thousand words");
+ } else {
+ assertThat(contentDescription).isNull();
+ }
}
/**
@@ -353,9 +375,9 @@
mActivity.tapBuy();
// First make sure the UI is shown...
- final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+ final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
// Then make sure it does not have the custom views on it...
- assertThat(saveUi.findObject(By.res(packageName, "static_text"))).isNull();
+ assertThat(saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT))).isNull();
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
index b7acfc6..92a379c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
@@ -25,9 +25,12 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.service.autofill.BatchUpdates;
import android.service.autofill.CharSequenceTransformation;
import android.service.autofill.CustomDescription;
import android.service.autofill.ImageTransformation;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.Validator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.test.uiautomator.By;
@@ -36,7 +39,6 @@
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -47,7 +49,7 @@
public class CustomDescriptionTest extends AutoFillServiceTestCase {
@Rule
public final AutofillActivityTestRule<LoginActivity> mActivityRule =
- new AutofillActivityTestRule<>(LoginActivity.class);
+ new AutofillActivityTestRule<>(LoginActivity.class);
private LoginActivity mActivity;
@@ -56,11 +58,6 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
/**
* Base test
*
@@ -96,7 +93,7 @@
uiVerifier.run();
}
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
sReplier.getNextSaveRequest();
assertNoDanglingSessions();
@@ -105,13 +102,13 @@
@Test
public void validTransformation() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
CharSequenceTransformation trans1 = new CharSequenceTransformation
.Builder(usernameId, Pattern.compile("(.*)"), "$1")
.addField(passwordId, Pattern.compile(".*(..)"), "..$1")
.build();
+ @SuppressWarnings("deprecation")
ImageTransformation trans2 = new ImageTransformation
.Builder(usernameId, Pattern.compile(".*"),
R.drawable.android).build();
@@ -120,31 +117,253 @@
.addChild(R.id.first, trans1)
.addChild(R.id.img, trans2)
.build();
- }, () -> assertSaveUiWithCustomDescriptionIsShown("usernm..wd"));
+ }, () -> assertSaveUiIsShownWithTwoLines("usernm..wd"));
+ }
+
+ @Test
+ public void validTransformationWithOneTemplateUpdate() throws Exception {
+ testCustomDescription((usernameId, passwordId) -> {
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+ CharSequenceTransformation trans1 = new CharSequenceTransformation
+ .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+ .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+ .build();
+ @SuppressWarnings("deprecation")
+ ImageTransformation trans2 = new ImageTransformation
+ .Builder(usernameId, Pattern.compile(".*"),
+ R.drawable.android).build();
+ RemoteViews update = newTemplate(0); // layout id not really used
+ update.setViewVisibility(R.id.second, View.GONE);
+ Validator condition = new RegexValidator(usernameId, Pattern.compile(".*"));
+
+ return new CustomDescription.Builder(presentation)
+ .addChild(R.id.first, trans1)
+ .addChild(R.id.img, trans2)
+ .batchUpdate(condition,
+ new BatchUpdates.Builder().updateTemplate(update).build())
+ .build();
+ }, () -> assertSaveUiIsShownWithJustOneLine("usernm..wd"));
+ }
+
+ @Test
+ public void validTransformationWithMultipleTemplateUpdates() throws Exception {
+ testCustomDescription((usernameId, passwordId) -> {
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+ CharSequenceTransformation trans1 = new CharSequenceTransformation.Builder(usernameId,
+ Pattern.compile("(.*)"), "$1")
+ .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+ .build();
+ @SuppressWarnings("deprecation")
+ ImageTransformation trans2 = new ImageTransformation.Builder(usernameId,
+ Pattern.compile(".*"), R.drawable.android)
+ .build();
+
+ Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+ Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+
+ // Line 1 updates
+ RemoteViews update1 = newTemplate(666); // layout id not really used
+ update1.setContentDescription(R.id.first, "First am I"); // valid
+ RemoteViews update2 = newTemplate(0); // layout id not really used
+ update2.setViewVisibility(R.id.first, View.GONE); // invalid
+
+ // Line 2 updates
+ RemoteViews update3 = newTemplate(-666); // layout id not really used
+ update3.setTextViewText(R.id.second, "First of his second name"); // valid
+ RemoteViews update4 = newTemplate(0); // layout id not really used
+ update4.setTextViewText(R.id.second, "SECOND of his second name"); // invalid
+
+ return new CustomDescription.Builder(presentation)
+ .addChild(R.id.first, trans1)
+ .addChild(R.id.img, trans2)
+ .batchUpdate(validCondition,
+ new BatchUpdates.Builder().updateTemplate(update1).build())
+ .batchUpdate(invalidCondition,
+ new BatchUpdates.Builder().updateTemplate(update2).build())
+ .batchUpdate(validCondition,
+ new BatchUpdates.Builder().updateTemplate(update3).build())
+ .batchUpdate(invalidCondition,
+ new BatchUpdates.Builder().updateTemplate(update4).build())
+ .build();
+ }, () -> assertSaveUiWithLinesIsShown(
+ (line1) -> assertWithMessage("Wrong content description for line1")
+ .that(line1.getContentDescription()).isEqualTo("First am I"),
+ (line2) -> assertWithMessage("Wrong text for line2").that(line2.getText())
+ .isEqualTo("First of his second name"),
+ null));
+ }
+
+ @Test
+ public void testMultipleBatchUpdates_noConditionPass() throws Exception {
+ multipleBatchUpdatesTest(BatchUpdatesConditionType.NONE_PASS);
+ }
+
+ @Test
+ public void testMultipleBatchUpdates_secondConditionPass() throws Exception {
+ multipleBatchUpdatesTest(BatchUpdatesConditionType.SECOND_PASS);
+ }
+
+ @Test
+ public void testMultipleBatchUpdates_thirdConditionPass() throws Exception {
+ multipleBatchUpdatesTest(BatchUpdatesConditionType.THIRD_PASS);
+ }
+
+ @Test
+ public void testMultipleBatchUpdates_allConditionsPass() throws Exception {
+ multipleBatchUpdatesTest(BatchUpdatesConditionType.ALL_PASS);
+ }
+
+ private enum BatchUpdatesConditionType {
+ NONE_PASS,
+ SECOND_PASS,
+ THIRD_PASS,
+ ALL_PASS
+ }
+
+ /**
+ * Tests a custom description that has 3 transformations, one applied directly and the other
+ * 2 in batch updates.
+ *
+ * @param conditionsType defines which batch updates conditions will pass.
+ */
+ private void multipleBatchUpdatesTest(BatchUpdatesConditionType conditionsType)
+ throws Exception {
+
+ final boolean line2Pass = conditionsType == BatchUpdatesConditionType.SECOND_PASS
+ || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+ final boolean line3Pass = conditionsType == BatchUpdatesConditionType.THIRD_PASS
+ || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+
+ final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+ .that(line1.getText()).isEqualTo("L1-u");
+
+ final Visitor<UiObject2> line2Visitor;
+ if (line2Pass) {
+ line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+ .that(line2.getText()).isEqualTo("L2-u");
+ } else {
+ line2Visitor = null;
+ }
+
+ final Visitor<UiObject2> line3Visitor;
+ if (line3Pass) {
+ line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+ .that(line3.getText()).isEqualTo("L3-p");
+ } else {
+ line3Visitor = null;
+ }
+
+ testCustomDescription((usernameId, passwordId) -> {
+ Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+ Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+ Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+ final RemoteViews presentation =
+ newTemplate(R.layout.three_horizontal_text_fields_last_two_invisible);
+
+ final CharSequenceTransformation line1Transformation =
+ new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+ .build();
+
+ final CharSequenceTransformation line2Transformation =
+ new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+ .build();
+ final RemoteViews line2Updates = newTemplate(666); // layout id not really used
+ line2Updates.setViewVisibility(R.id.second, View.VISIBLE);
+
+ final CharSequenceTransformation line3Transformation =
+ new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+ .build();
+ final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+ line3Updates.setViewVisibility(R.id.third, View.VISIBLE);
+
+ return new CustomDescription.Builder(presentation)
+ .addChild(R.id.first, line1Transformation)
+ .batchUpdate(line2Pass ? validCondition : invalidCondition,
+ new BatchUpdates.Builder()
+ .transformChild(R.id.second, line2Transformation)
+ .updateTemplate(line2Updates)
+ .build())
+ .batchUpdate(line3Pass ? validCondition : invalidCondition,
+ new BatchUpdates.Builder()
+ .transformChild(R.id.third, line3Transformation)
+ .updateTemplate(line3Updates)
+ .build())
+ .build();
+ }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
+ }
+
+ @Test
+ public void testBatchUpdatesApplyUpdateFirstThenTransformations() throws Exception {
+
+ final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+ .that(line1.getText()).isEqualTo("L1-u");
+ final Visitor<UiObject2> line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+ .that(line2.getText()).isEqualTo("L2-u");
+ final Visitor<UiObject2> line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+ .that(line3.getText()).isEqualTo("L3-p");
+
+ testCustomDescription((usernameId, passwordId) -> {
+ Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+ Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+ Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+ final RemoteViews presentation =
+ newTemplate(R.layout.two_horizontal_text_fields);
+
+ final CharSequenceTransformation line1Transformation =
+ new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+ .build();
+
+ final CharSequenceTransformation line2Transformation =
+ new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+ .build();
+
+ final CharSequenceTransformation line3Transformation =
+ new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+ .build();
+ final RemoteViews line3Presentation = newTemplate(R.layout.third_line_only);
+ final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+ line3Updates.addView(R.id.parent, line3Presentation);
+
+ return new CustomDescription.Builder(presentation)
+ .addChild(R.id.first, line1Transformation)
+ .batchUpdate(validCondition,
+ new BatchUpdates.Builder()
+ .transformChild(R.id.second, line2Transformation)
+ .build())
+ .batchUpdate(validCondition,
+ new BatchUpdates.Builder()
+ .updateTemplate(line3Updates)
+ .transformChild(R.id.third, line3Transformation)
+ .build())
+ .build();
+ }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
}
@Test
public void badImageTransformation() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
- ImageTransformation trans = new ImageTransformation
- .Builder(usernameId, Pattern.compile(".*"), 1)
- .build();
+ @SuppressWarnings("deprecation")
+ ImageTransformation trans = new ImageTransformation.Builder(usernameId,
+ Pattern.compile(".*"), 1).build();
return new CustomDescription.Builder(presentation)
.addChild(R.id.img, trans)
.build();
- }, () -> assertSaveUiWithCustomDescriptionIsShown() );
+ }, () -> assertSaveUiWithCustomDescriptionIsShown());
}
@Test
public void unusedImageTransformation() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+ @SuppressWarnings("deprecation")
ImageTransformation trans = new ImageTransformation
.Builder(usernameId, Pattern.compile("invalid"), R.drawable.android)
.build();
@@ -158,9 +377,9 @@
@Test
public void applyImageTransformationToTextView() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+ @SuppressWarnings("deprecation")
ImageTransformation trans = new ImageTransformation
.Builder(usernameId, Pattern.compile(".*"), R.drawable.android)
.build();
@@ -174,8 +393,7 @@
@Test
public void failFirstFailAll() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
CharSequenceTransformation trans = new CharSequenceTransformation
.Builder(usernameId, Pattern.compile("(.*)"), "$42")
@@ -191,8 +409,7 @@
@Test
public void failSecondFailAll() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
CharSequenceTransformation trans = new CharSequenceTransformation
.Builder(usernameId, Pattern.compile("(.*)"), "$1")
@@ -208,8 +425,7 @@
@Test
public void applyCharSequenceTransformationToImageView() throws Exception {
testCustomDescription((usernameId, passwordId) -> {
- RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
CharSequenceTransformation trans = new CharSequenceTransformation
.Builder(usernameId, Pattern.compile("(.*)"), "$1")
@@ -232,8 +448,7 @@
final CharSequenceTransformation secondTrans = new CharSequenceTransformation
.Builder(usernameId, Pattern.compile("(MARCO)"), "POLO")
.build();
- final RemoteViews presentation = new RemoteViews(getContext().getPackageName(),
- R.layout.two_horizontal_text_fields);
+ RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
final CustomDescription customDescription = new CustomDescription.Builder(presentation)
.addChild(R.id.first, firstTrans)
.addChild(R.id.first, secondTrans)
@@ -256,7 +471,7 @@
mActivity.tapLogin();
final String expectedText = matchFirst ? "polo" : "POLO";
- assertSaveUiWithCustomDescriptionIsShown(expectedText);
+ assertSaveUiIsShownWithTwoLines(expectedText);
}
@Test
@@ -269,18 +484,30 @@
multipleTransformationsForSameFieldTest(false);
}
+ private RemoteViews newTemplate(int resourceId) {
+ return new RemoteViews(getContext().getPackageName(), resourceId);
+ }
+
+ private UiObject2 assertSaveUiShowing() {
+ try {
+ return mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private void assertSaveUiWithoutCustomDescriptionIsShown() {
// First make sure the UI is shown...
- final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ final UiObject2 saveUi = assertSaveUiShowing();
// Then make sure it does not have the custom view on it.
- assertWithMessage("found static_text on SaveUI (%s)", sUiBot.getChildrenAsText(saveUi))
+ assertWithMessage("found static_text on SaveUI (%s)", mUiBot.getChildrenAsText(saveUi))
.that(saveUi.findObject(By.res(mPackageName, "static_text"))).isNull();
}
private UiObject2 assertSaveUiWithCustomDescriptionIsShown() {
// First make sure the UI is shown...
- final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ final UiObject2 saveUi = assertSaveUiShowing();
// Then make sure it does have the custom view on it...
final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
@@ -290,10 +517,59 @@
return saveUi;
}
- private void assertSaveUiWithCustomDescriptionIsShown(String expectedText) {
+ /**
+ * Asserts the save ui only has {@code first} and {@code second} lines (i.e, {@code third} is
+ * invisible), but only {@code first} has text.
+ */
+ private UiObject2 assertSaveUiIsShownWithTwoLines(String expectedTextOnFirst) {
+ return assertSaveUiWithLinesIsShown(
+ (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+ .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+ (line2) -> assertWithMessage("Wrong text for child with id 'second'")
+ .that(line2.getText()).isNull(),
+ null);
+ }
+
+ /**
+ * Asserts the save ui only has {@code first} line (i.e., {@code second} and {@code third} are
+ * invisible).
+ */
+ private void assertSaveUiIsShownWithJustOneLine(String expectedTextOnFirst) {
+ assertSaveUiWithLinesIsShown(
+ (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+ .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+ null, null);
+ }
+
+ private UiObject2 assertSaveUiWithLinesIsShown(@Nullable Visitor<UiObject2> line1Visitor,
+ @Nullable Visitor<UiObject2> line2Visitor, @Nullable Visitor<UiObject2> line3Visitor) {
final UiObject2 saveUi = assertSaveUiWithCustomDescriptionIsShown();
- assertWithMessage("didn't find '%s' on SaveUI (%s)", expectedText,
- sUiBot.getChildrenAsText(saveUi))
- .that(saveUi.findObject(By.text(expectedText))).isNotNull();
+ assertSaveUiChild(saveUi, "first", line1Visitor);
+ assertSaveUiChild(saveUi, "second", line2Visitor);
+ assertSaveUiChild(saveUi, "third", line3Visitor);
+ return saveUi;
+ }
+
+ /**
+ * Asserts the contents of save UI child element.
+ *
+ * @param saveUi save UI
+ * @param childId resource id of the child
+ * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
+ * child with it.
+ */
+ private void assertSaveUiChild(@NonNull UiObject2 saveUi, @NonNull String childId,
+ @Nullable Visitor<UiObject2> assertion) {
+ final UiObject2 child = saveUi.findObject(By.res(mPackageName, childId));
+ if (assertion != null) {
+ assertWithMessage("Didn't find child with id '%s'", childId).that(child).isNotNull();
+ try {
+ assertion.visit(child);
+ } catch (Throwable t) {
+ throw new AssertionError("Error on child '" + childId + "'", t);
+ }
+ } else {
+ assertWithMessage("Shouldn't find child with id '%s'", childId).that(child).isNull();
+ }
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
new file mode 100644
index 0000000..2a50b6e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.Transformation;
+import android.service.autofill.Validator;
+import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CustomDescriptionUnitTest {
+
+ private final CustomDescription.Builder mBuilder =
+ new CustomDescription.Builder(mock(RemoteViews.class));
+ private final BatchUpdates mValidUpdate =
+ new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
+ private final Validator mValidCondition = mock(InternalValidator.class);
+
+ @Test
+ public void testNullConstructor() {
+ assertThrows(NullPointerException.class, () -> new CustomDescription.Builder(null));
+ }
+
+ @Test
+ public void testAddChild_null() {
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.addChild(42, null));
+ }
+
+ @Test
+ public void testAddChild_invalidTransformation() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.addChild(42, mock(Transformation.class)));
+ }
+
+ @Test
+ public void testBatchUpdate_nullCondition() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.batchUpdate(null, mValidUpdate));
+ }
+
+ @Test
+ public void testBatchUpdate_invalidCondition() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
+ }
+
+ @Test
+ public void testBatchUpdate_nullUpdates() {
+ assertThrows(NullPointerException.class,
+ () -> mBuilder.batchUpdate(mValidCondition, null));
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
index 33f0943..7b8a42c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -61,12 +61,13 @@
*/
@Test
public final void testTapLink_changeOrientationThenTapBack() throws Exception {
- sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+ mUiBot.setScreenOrientation(UiBot.PORTRAIT);
try {
saveUiRestoredAfterTappingLinkTest(
PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
} finally {
- sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+ mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+ cleanUpAfterScreenOrientationIsBackToPortrait();
}
}
@@ -83,6 +84,9 @@
protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
throws Exception;
+ protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+ }
+
/**
* Tests scenarios when user taps a link in the custom description, taps back to return to the
* activity with the Save UI, and touch outside the Save UI to dismiss it.
@@ -192,6 +196,9 @@
saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
}
+ protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+ throws Exception;
+
@Test
public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
throws Exception {
@@ -201,6 +208,18 @@
protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
throws Exception;
+ @Test
+ public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception {
+ tapLinkAfterUpdateAppliedTest(true);
+ }
+
+ @Test
+ public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception {
+ tapLinkAfterUpdateAppliedTest(false);
+ }
+
+ protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception;
+
enum PostSaveLinkTappedAction {
TAP_BACK_BUTTON,
ROTATE_THEN_TAP_BACK_BUTTON,
@@ -213,41 +232,57 @@
TAP_YES_ON_SAVE_UI
}
- protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
- throws Exception;
-
protected final void startActivity(Class<?> clazz) {
mContext.startActivity(new Intent(mContext, clazz));
}
- protected final CustomDescription newCustomDescription(
+ protected RemoteViews newTemplate() {
+ final RemoteViews presentation = new RemoteViews(mPackageName,
+ R.layout.custom_description_with_link);
+ return presentation;
+ }
+
+ protected final CustomDescription.Builder newCustomDescriptionBuilder(
Class<? extends Activity> activityClass) {
final Intent intent = new Intent(mContext, activityClass);
intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- return newCustomDescription(intent);
+ return newCustomDescriptionBuilder(intent);
+ }
+
+ protected final CustomDescription newCustomDescription(
+ Class<? extends Activity> activityClass) {
+ return newCustomDescriptionBuilder(activityClass).build();
+ }
+
+ protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) {
+ final RemoteViews presentation = newTemplate();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
+ return new CustomDescription.Builder(presentation);
}
protected final CustomDescription newCustomDescription(Intent intent) {
- final RemoteViews presentation = new RemoteViews(mPackageName,
- R.layout.custom_description_with_link);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
- presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
- return new CustomDescription.Builder(presentation).build();
+ return newCustomDescriptionBuilder(intent).build();
}
- protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) {
+ protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception {
+ return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
+ }
+
+ protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText)
+ throws Exception {
// First make sure the UI is shown...
- final UiObject2 saveUi = sUiBot.assertSaveShowing(saveType);
+ final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType);
// Then make sure it does have the custom view with link on it...
- getLink(saveUi);
+ final UiObject2 link = getLink(saveUi);
+ assertThat(link.getText()).isEqualTo(expectedText);
return saveUi;
}
protected final UiObject2 getLink(final UiObject2 container) {
- final UiObject2 button = container.findObject(By.res(mPackageName, ID_LINK));
- assertThat(button).isNotNull();
- assertThat(button.getText()).isEqualTo("DON'T TAP ME!");
- return button;
+ final UiObject2 link = container.findObject(By.res(mPackageName, ID_LINK));
+ assertThat(link).isNotNull();
+ return link;
}
protected final void tapSaveUiLink(UiObject2 saveUi) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
new file mode 100644
index 0000000..741fdda
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.Dataset;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DatasetTest {
+
+ private final AutofillId mId = new AutofillId(42);
+ private final AutofillValue mValue = AutofillValue.forText("ValuableLikeGold");
+ private final Pattern mFilter = Pattern.compile("whatever");
+
+ private final RemoteViews mPresentation = mock(RemoteViews.class);
+
+ @Test
+ public void testBuilder_nullPresentation() {
+ assertThrows(NullPointerException.class, () -> new Dataset.Builder(null));
+ }
+
+ @Test
+ public void testBuilder_setValueNullId() {
+ final Dataset.Builder builder = new Dataset.Builder(mPresentation);
+ assertThrows(NullPointerException.class, () -> builder.setValue(null, mValue));
+ }
+
+ @Test
+ public void testBuilder_setValueWithoutPresentation() {
+ // Just assert that it builds without throwing an exception.
+ assertThat(new Dataset.Builder().setValue(mId, mValue).build()).isNotNull();
+ }
+
+ @Test
+ public void testBuilder_setValueWithNullPresentation() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+ (RemoteViews) null));
+ }
+
+ @Test
+ public void testBuilder_setFilteredValueWithNullFilter() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+ (Pattern) null));
+ }
+
+ @Test
+ public void testBuilder_setFilteredValueWithPresentationNullFilter() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, null,
+ mPresentation));
+ }
+
+ @Test
+ public void testBuilder_setFilteredValueWithPresentationNullPresentation() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+ null));
+ }
+
+ @Test
+ public void testBuilder_setFilteredValueWithoutPresentation() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue, mFilter));
+ }
+
+ @Test
+ public void testBuild_noValues() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ assertThrows(IllegalStateException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testNoMoreInteractionsAfterBuild() {
+ final Dataset.Builder builder = new Dataset.Builder();
+ builder.setValue(mId, mValue, mPresentation);
+ assertThat(builder.build()).isNotNull();
+ assertThrows(IllegalStateException.class, () -> builder.build());
+ assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue));
+ assertThrows(IllegalStateException.class,
+ () -> builder.setValue(mId, mValue, mPresentation));
+ assertThrows(IllegalStateException.class,
+ () -> builder.setValue(mId, mValue, mFilter));
+ assertThrows(IllegalStateException.class,
+ () -> builder.setValue(mId, mValue, mFilter, mPresentation));
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
index d977ac6..55bc654 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
@@ -31,7 +31,6 @@
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.icu.util.Calendar;
-import org.junit.After;
import org.junit.Test;
/**
@@ -42,11 +41,6 @@
protected abstract T getDatePickerActivity();
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
@Test
public void testAutoFillAndSave() throws Exception {
final T activity = getDatePickerActivity();
@@ -79,7 +73,7 @@
assertNumberOfChildren(fillRequest.structure, ID_DATE_PICKER, 0);
// Auto-fill it.
- sUiBot.selectDataset("The end of the world");
+ mUiBot.selectDataset("The end of the world");
// Check the results.
activity.assertAutoFilled();
@@ -88,7 +82,7 @@
activity.setDate(2010, 11, 12);
activity.tapOk();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
new file mode 100644
index 0000000..24cd5bf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Activity that has buttons to launch dialogs that should then be autofillable.
+ */
+public class DialogLauncherActivity extends AbstractAutoFillActivity {
+
+ private static final String TAG = "DialogLauncherActivity";
+
+ private FillExpectation mExpectation;
+ private LoginDialog mDialog;
+ Button mLaunchButton;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.dialog_launcher_activity);
+ mLaunchButton = findViewById(R.id.launch_button);
+ mDialog = new LoginDialog(this);
+ mLaunchButton.setOnClickListener((v) -> mDialog.show());
+ }
+
+ void onUsername(Visitor<EditText> v) {
+ syncRunOnUiThread(() -> v.visit(mDialog.mUsernameEditText));
+ }
+
+ void launchDialog(UiBot uiBot) throws Exception {
+ syncRunOnUiThread(() -> mLaunchButton.performClick());
+ // TODO: should assert by id, but it's not working
+ uiBot.assertShownByText("Username");
+ }
+
+ void maximizeDialog() {
+ final WindowManager wm = getWindowManager();
+ final Display display = wm.getDefaultDisplay();
+ final DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ syncRunOnUiThread(
+ () -> mDialog.getWindow().setLayout(metrics.widthPixels, metrics.heightPixels));
+ }
+
+ void expectAutofill(String username, String password) {
+ assertWithMessage("must call launchDialog first").that(mDialog.mUsernameEditText)
+ .isNotNull();
+ mExpectation = new FillExpectation(username, password);
+ mDialog.mUsernameEditText.addTextChangedListener(mExpectation.mCcUsernameWatcher);
+ mDialog.mPasswordEditText.addTextChangedListener(mExpectation.mCcPasswordWatcher);
+ }
+
+ void assertAutofilled() throws Exception {
+ assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+ if (mExpectation.mCcUsernameWatcher != null) {
+ mExpectation.mCcUsernameWatcher.assertAutoFilled();
+ }
+ if (mExpectation.mCcPasswordWatcher != null) {
+ mExpectation.mCcPasswordWatcher.assertAutoFilled();
+ }
+ }
+
+ private final class FillExpectation {
+ private final OneTimeTextWatcher mCcUsernameWatcher;
+ private final OneTimeTextWatcher mCcPasswordWatcher;
+
+ private FillExpectation(String username, String password) {
+ mCcUsernameWatcher = username == null ? null
+ : new OneTimeTextWatcher("username", mDialog.mUsernameEditText, username);
+ mCcPasswordWatcher = password == null ? null
+ : new OneTimeTextWatcher("password", mDialog.mPasswordEditText, password);
+ }
+
+ private FillExpectation(String username) {
+ this(username, null);
+ }
+ }
+
+ public final class LoginDialog extends AlertDialog {
+
+ private EditText mUsernameEditText;
+ private EditText mPasswordEditText;
+
+ public LoginDialog(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.login_activity);
+ mUsernameEditText = findViewById(R.id.username);
+ mPasswordEditText = findViewById(R.id.password);
+ }
+
+ // TODO(b/68816440): temporary hack to make sure tests pass - everything below should be
+ // removed
+ private IBinder mToken;
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mToken = DialogLauncherActivity.this.getWindow().getAttributes().token;
+ Log.v(TAG, "onAttachedToWindow(): " + mToken);
+ }
+
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams params) {
+ Log.v(TAG, "onWindowAttributesChanged: p.token=" + params.token + "hack=" + mToken);
+ params.token = mToken;
+
+ super.onWindowAttributesChanged(params);
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
new file mode 100644
index 0000000..981655d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DialogLauncherActivityTest extends AutoFillServiceTestCase {
+
+ @Rule
+ public final AutofillActivityTestRule<DialogLauncherActivity> mActivityRule =
+ new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class);
+
+ private DialogLauncherActivity mActivity;
+
+ @Before
+ public void setActivity() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testAutofill_noDatasets() throws Exception {
+ autofillNoDatasetsTest(false);
+ }
+
+ @Test
+ public void testAutofill_noDatasets_afterResizing() throws Exception {
+ autofillNoDatasetsTest(true);
+ }
+
+ private void autofillNoDatasetsTest(boolean resize) throws Exception {
+ enableService();
+ mActivity.launchDialog(mUiBot);
+
+ if (resize) {
+ mActivity.maximizeDialog();
+ }
+
+ // Set expectations.
+ sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+ // Trigger autofill.
+ mActivity.onUsername(View::requestFocus);
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+ // Asserts results.
+ try {
+ mUiBot.assertNoDatasets();
+ // Make sure nodes were properly generated.
+ assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+ assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+ } catch (AssertionError e) {
+ Helper.dumpStructure("D'OH!", fillRequest.structure);
+ throw e;
+ }
+ }
+
+ @Test
+ public void testAutofill_oneDataset() throws Exception {
+ autofillOneDatasetTest(false);
+ }
+
+ @Test
+ public void testAutofill_oneDataset_afterResizing() throws Exception {
+ autofillOneDatasetTest(true);
+ }
+
+ private void autofillOneDatasetTest(boolean resize) throws Exception {
+ enableService();
+ mActivity.launchDialog(mUiBot);
+
+ if (resize) {
+ mActivity.maximizeDialog();
+ }
+
+ // Set expectations.
+ mActivity.expectAutofill("dude", "sweet");
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Dude"))
+ .build());
+
+ // Trigger autofill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Asserts results.
+ mUiBot.selectDataset("The Dude");
+ mActivity.assertAutofilled();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
new file mode 100644
index 0000000..a7d3519
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.service.autofill.FillResponse;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
+ */
+public class DisableAutofillTest extends AutoFillServiceTestCase {
+
+ private static final String TAG = "DisableAutofillTest";
+
+ @Before
+ public void preTestCleanup() {
+ Helper.preTestCleanup();
+ }
+
+ private SimpleSaveActivity startSimpleSaveActivity() throws Exception {
+ final Intent intent = new Intent(mContext, SimpleSaveActivity.class);
+ mContext.startActivity(intent);
+ mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
+ return SimpleSaveActivity.getInstance();
+ }
+
+ private PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
+ final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class);
+ mContext.startActivity(intent);
+ mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
+ return PreSimpleSaveActivity.getInstance();
+ }
+
+ /**
+ * Defines what to do after the activity being tested is launched.
+ */
+ enum PostLaunchAction {
+ /**
+ * Used when the service disables autofill in the fill response for this activty. As such:
+ *
+ * <ol>
+ * <li>There should be a fill request on {@code sReplier}.
+ * <li>The first UI focus should generate a
+ * {@link android.view.autofill.AutofillManager.AutofillCallback#EVENT_INPUT_UNAVAILABLE}
+ * event.
+ * <li>Subsequent UI focus should not trigger events.
+ * </ol>
+ */
+ ASSERT_DISABLING,
+
+ /**
+ * Used when the service already disabled autofill prior to launching activty. As such:
+ *
+ * <ol>
+ * <li>There should be no fill request on {@code sReplier}.
+ * <li>There should be no callback calls when UI is focused
+ * </ol>
+ */
+ ASSERT_DISABLED,
+
+ /**
+ * Used when autofill is enabled, so it tries to autofill the activity.
+ */
+ ASSERT_ENABLED_AND_AUTOFILL
+ }
+
+ private void launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
+ Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+ sReplier.assertNumberUnhandledFillRequests(0);
+
+ if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(SimpleSaveActivity.ID_INPUT, "id")
+ .setField(SimpleSaveActivity.ID_PASSWORD, "pass")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+
+ }
+
+ final SimpleSaveActivity activity = startSimpleSaveActivity();
+ final MyAutofillCallback callback = activity.registerCallback();
+
+ try {
+ // Trigger autofill
+ activity.syncRunOnUiThread(() -> activity.mInput.requestFocus());
+
+ if (action == PostLaunchAction.ASSERT_DISABLING) {
+ callback.assertUiUnavailableEvent(activity.mInput);
+ sReplier.getNextFillRequest();
+
+ // Make sure other fields are not triggered.
+ activity.syncRunOnUiThread(() -> activity.mPassword.requestFocus());
+ callback.assertNotCalled();
+ } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+ // Make sure forced requests are ignored as well.
+ activity.getAutofillManager().requestAutofill(activity.mInput);
+ callback.assertNotCalled();
+ } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+ callback.assertUiShownEvent(activity.mInput);
+ sReplier.getNextFillRequest();
+ final SimpleSaveActivity.FillExpectation autofillExpectation =
+ activity.expectAutoFill("id", "pass");
+ mUiBot.selectDataset("YO");
+ autofillExpectation.assertAutoFilled();
+ }
+
+ // Asserts isEnabled() status.
+ assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ } finally {
+ activity.unregisterCallback();
+ activity.finish();
+ }
+ sReplier.assertNumberUnhandledFillRequests(0);
+ }
+
+ private void launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
+ Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+ sReplier.assertNumberUnhandledFillRequests(0);
+
+ if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(PreSimpleSaveActivity.ID_PRE_INPUT, "yo")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+ }
+
+ final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
+ final MyAutofillCallback callback = activity.registerCallback();
+
+ try {
+ // Trigger autofill
+ activity.syncRunOnUiThread(() -> activity.mPreInput.requestFocus());
+
+ if (action == PostLaunchAction.ASSERT_DISABLING) {
+ callback.assertUiUnavailableEvent(activity.mPreInput);
+ sReplier.getNextFillRequest();
+ } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+ activity.getAutofillManager().requestAutofill(activity.mPreInput);
+ callback.assertNotCalled();
+ } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+ callback.assertUiShownEvent(activity.mPreInput);
+ sReplier.getNextFillRequest();
+ final PreSimpleSaveActivity.FillExpectation autofillExpectation =
+ activity.expectAutoFill("yo");
+ mUiBot.selectDataset("YO");
+ autofillExpectation.assertAutoFilled();
+ }
+
+ // Asserts isEnabled() status.
+ assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ } finally {
+ activity.unregisterCallback();
+ activity.finish();
+ }
+ sReplier.assertNumberUnhandledFillRequests(0);
+ }
+
+ @Test
+ public void testDisableApp() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(
+ new CannedFillResponse.Builder().disableAutofill(Long.MAX_VALUE).build());
+
+ // Trigger autofill for the first time.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+ // Launch activity again.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+ // Now try it using a different activity - should be disabled too.
+ launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+ }
+
+ @Test
+ public void testDisableAppThenWaitToReenableIt() throws Exception {
+ // Set service.
+ enableService();
+
+ // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+ final long duration = 2 * Timeouts.ACTIVITY_RESURRECTION.ms() + 500;
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
+
+ // Trigger autofill for the first time.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+ // Launch activity again.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+ // Wait for the timeout, then try again, autofilling it this time.
+ SystemClock.sleep(duration + 1);
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+ // Also try it on another activity.
+ launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ }
+
+ @Test
+ public void testDisableAppThenResetServiceToReenableIt() throws Exception {
+ enableService();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .disableAutofill(Long.MAX_VALUE).build());
+
+ // Trigger autofill for the first time.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+ // Launch activity again.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+ // Then "reset" service to re-enable autofill.
+ disableService();
+ enableService();
+
+ // Try again on activity that disabled it.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+ // Try again on other activity.
+ launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ }
+
+ @Test
+ public void testDisableActivity() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .disableAutofill(Long.MAX_VALUE)
+ .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+ .build());
+
+ // Trigger autofill for the first time.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+ // Launch activity again.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+ // Now try it using a different activity - should work.
+ launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ }
+
+ @Test
+ public void testDisableActivityThenWaitToReenableIt() throws Exception {
+ // Set service.
+ enableService();
+
+ // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+ final long duration = 2 * Timeouts.ACTIVITY_RESURRECTION.ms() + 500;
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .disableAutofill(duration)
+ .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+ .build());
+
+ // Trigger autofill for the first time.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+ // Launch activity again.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+ // Make sure other app is working.
+ launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+ // Wait for the timeout, then try again, autofilling it this time.
+ SystemClock.sleep(duration + 1);
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ }
+
+ @Test
+ public void testDisableActivityThenResetServiceToReenableIt() throws Exception {
+ enableService();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .disableAutofill(Long.MAX_VALUE)
+ .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+ .build());
+
+ // Trigger autofill for the first time.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+ // Launch activity again.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+ // Make sure other app is working.
+ launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+ // Then "reset" service to re-enable autofill.
+ disableService();
+ enableService();
+
+ // Try again on activity that disabled it.
+ launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+ }
+
+ private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
+ throws Exception {
+ Timeouts.ACTIVITY_RESURRECTION.run(
+ "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
+ () -> {
+ return activity.getAutofillManager().isEnabled() == expected
+ ? Boolean.TRUE : null;
+ });
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 0e59e2f..e44f510 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -18,9 +18,9 @@
import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
import static android.autofillservice.cts.DuplicateIdActivity.DUPLICATE_ID;
-import static android.autofillservice.cts.Helper.runShellCommand;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
@@ -46,9 +46,9 @@
private DuplicateIdActivity mActivity;
@Before
- public void setup() {
- Helper.disableAutoRotation(sUiBot);
- sUiBot.setScreenOrientation(0);
+ public void setup() throws Exception {
+ Helper.disableAutoRotation(mUiBot);
+ mUiBot.setScreenOrientation(0);
mActivity = mActivityRule.getActivity();
}
@@ -112,13 +112,21 @@
sReplier.addResponse(NO_RESPONSE);
// Force rotation to force onDestroy->onCreate cycle
- sUiBot.setScreenOrientation(1);
+ mUiBot.setScreenOrientation(1);
+ // Wait context and Views being recreated in rotation
+ mUiBot.assertShownByRelativeId(DUPLICATE_ID);
+
+ // Because service returned a null response, rotation will trigger another request.
+ sReplier.addResponse(NO_RESPONSE);
// Select other field to trigger new partition
runShellCommand("input keyevent KEYCODE_TAB");
request = sReplier.getNextFillRequest();
+ // Ignore 2nd request.
+ sReplier.getNextFillRequest();
+
views = findViews(request);
AutofillId recreatedId1 = views[0].getAutofillId();
AutofillId recreatedId2 = views[1].getAutofillId();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/EditDistanceScorerTest.java b/tests/autofillservice/src/android/autofillservice/cts/EditDistanceScorerTest.java
new file mode 100644
index 0000000..0afa747
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/EditDistanceScorerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.assertFloat;
+
+import android.service.autofill.EditDistanceScorer;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class EditDistanceScorerTest {
+
+ private final EditDistanceScorer mScorer = EditDistanceScorer.getInstance();
+
+ @Test
+ public void testGetScore_nullValue() {
+ assertFloat(mScorer.getScore(null, "D'OH!"), 0);
+ }
+
+ @Test
+ public void testGetScore_nonTextValue() {
+ assertFloat(mScorer.getScore(AutofillValue.forToggle(true), "D'OH!"), 0);
+ }
+
+ @Test
+ public void testGetScore_nullUserData() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), null), 0);
+ }
+
+ @Test
+ public void testGetScore_fullMatch() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1);
+ }
+
+ @Test
+ public void testGetScore_fullMatchMixedCase() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1);
+ }
+
+ // TODO(b/70291841): might need to change it once it supports different sizes
+ @Test
+ public void testGetScore_mismatchDifferentSizes() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("One"), "MoreThanOne"), 0);
+ assertFloat(mScorer.getScore(AutofillValue.forText("MoreThanOne"), "One"), 0);
+ }
+
+ @Test
+ public void testGetScore_partialMatch() {
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F);
+ assertFloat(mScorer.getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index b2dd1d4..ba6f7a1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -70,7 +70,7 @@
// Trigger auto-fill.
mFatActivity.onInput((v) -> v.requestFocus());
final FillRequest fillRequest = sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// TODO: should only have 5 children, but there is an extra
// TextView that's probably coming from the title. For now we're just ignoring it, but
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
new file mode 100644
index 0000000..24bc2a3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted;
+import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
+import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.Helper.FieldClassificationResult;
+import android.autofillservice.cts.common.SettingsStateChangerRule;
+import android.content.Context;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.UserData;
+import android.support.test.InstrumentationRegistry;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+
+public class FieldsClassificationTest extends AutoFillServiceTestCase {
+
+ private static final Context sContext = InstrumentationRegistry.getContext();
+
+ @ClassRule
+ public static final SettingsStateChangerRule sFeatureEnabler =
+ new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+ new SettingsStateChangerRule(sContext,
+ AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+ new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "10");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMinValueChanger =
+ new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "5");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+ new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+ @Rule
+ public final AutofillActivityTestRule<GridActivity> mActivityRule =
+ new AutofillActivityTestRule<GridActivity>(GridActivity.class);
+
+
+ private GridActivity mActivity;
+ private AutofillManager mAfm;
+
+ @Before
+ public void setFixtures() {
+ mActivity = mActivityRule.getActivity();
+ mAfm = mActivity.getAutofillManager();
+ }
+
+ @Test
+ public void testFeatureIsEnabled() throws Exception {
+ enableService();
+ assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
+
+ disableService();
+ assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
+ }
+
+ @Test
+ public void testGetAlgorithm() throws Exception {
+ enableService();
+
+ // Check algorithms
+ final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
+ assertThat(names.size()).isAtLeast(1);
+ final String defaultAlgorithm = getDefaultAlgorithm();
+ assertThat(defaultAlgorithm).isNotEmpty();
+ assertThat(names).contains(defaultAlgorithm);
+
+ // Checks invalid service
+ disableService();
+ assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
+ }
+
+ @Test
+ public void testHit_oneUserData_oneDetectableField() throws Exception {
+ simpleHitTest(false, null);
+ }
+
+ @Test
+ public void testHit_invalidAlgorithmIsIgnored() throws Exception {
+ // For simplicity's sake, let's assume that name will never be valid..
+ String invalidName = " ALGORITHM, Y NO INVALID? ";
+
+ simpleHitTest(true, invalidName);
+ }
+
+ @Test
+ public void testHit_userDataAlgorithmIsReset() throws Exception {
+ simpleHitTest(true, null);
+ }
+
+ private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final UserData.Builder userData = new UserData.Builder("myId", "FULLY");
+ if (setAlgorithm) {
+ userData.setFieldClassificationAlgorithm(algorithm, null);
+ }
+ mAfm.setUserData(userData.build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field = mActivity.getCell(1, 1);
+ final AutofillId fieldId = field.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "fully");
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForFieldsClassification(events.get(0), fieldId, "myId", 1,
+ getDefaultAlgorithm());
+ }
+
+ @Test
+ public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
+ manyUserData_oneDetectableField(true);
+ }
+
+ @Test
+ public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
+ manyUserData_oneDetectableField(false);
+ }
+
+ private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mAfm.setUserData(new UserData.Builder("1stId", "Iam1ST").add("2ndId", "Iam2ND").build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field = mActivity.getCell(1, 1);
+ final AutofillId fieldId = field.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ final String algorithm = getDefaultAlgorithm();
+ final String[] algorithms = { algorithm, algorithm };
+ // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
+ if (firstMatch) {
+ assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+ new FieldClassificationResult(fieldId, new String[] { "1stId", "2ndId" },
+ new float[] { 0.66F, 0.5F }, algorithms)});
+ } else {
+ assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+ new FieldClassificationResult(fieldId, new String[] { "2ndId", "1stId" },
+ new float[] { 0.66F, 0.5F }, algorithms) });
+ }
+ }
+
+ @Test
+ public void testHit_oneUserData_manyDetectableFields() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mAfm.setUserData(new UserData.Builder("myId", "FULLY").build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field1 = mActivity.getCell(1, 1);
+ final AutofillId fieldId1 = field1.getAutofillId();
+ final EditText field2 = mActivity.getCell(1, 2);
+ final AutofillId fieldId2 = field2.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(fieldId1, fieldId2)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field1);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "fully"); // 100%
+ mActivity.setText(1, 2, "fooly"); // 60%
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ final String algorithm = getDefaultAlgorithm();
+ assertFillEventForFieldsClassification(events.get(0),
+ new FieldClassificationResult[] {
+ new FieldClassificationResult(fieldId1, "myId", 1.0F, algorithm),
+ new FieldClassificationResult(fieldId2, "myId", 1.0F, algorithm),
+ });
+ }
+
+ @Test
+ public void testHit_manyUserData_manyDetectableFields() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mAfm.setUserData(new UserData.Builder("myId", "FULLY")
+ .add("totalMiss", "ZZZZZZZZZZ") // should not have matched any
+ .add("otherId", "EMPTY")
+ .build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field1 = mActivity.getCell(1, 1);
+ final AutofillId fieldId1 = field1.getAutofillId();
+ final EditText field2 = mActivity.getCell(1, 2);
+ final AutofillId fieldId2 = field2.getAutofillId();
+ final EditText field3 = mActivity.getCell(2, 1);
+ final AutofillId fieldId3 = field3.getAutofillId();
+ final EditText field4 = mActivity.getCell(2, 2);
+ final AutofillId fieldId4 = field4.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(fieldId1, fieldId2)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field1);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "fully"); // u1: 100% u2: 20%
+ mActivity.setText(1, 2, "empty"); // u1: 20% u2: 100%
+ mActivity.setText(2, 1, "fooly"); // u1: 60% u2: 20%
+ mActivity.setText(2, 2, "emppy"); // u1: 20% u2: 80%
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final String algorithm = getDefaultAlgorithm();
+ final String[] algorithms = { algorithm, algorithm };
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForFieldsClassification(events.get(0),
+ new FieldClassificationResult[] {
+ new FieldClassificationResult(fieldId1, new String[] { "myId", "otherId" },
+ new float[] { 1.0F, 0.2F }, algorithms),
+ new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" },
+ new float[] { 1.0F, 0.2F }, algorithms),
+ new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
+ new float[] { 0.6F, 0.2F }, algorithms),
+ new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"},
+ new float[] { 0.80F, 0.2F }, algorithms)});
+ }
+
+ @Test
+ public void testMiss() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mAfm.setUserData(new UserData.Builder("myId", "ABCDEF").build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field = mActivity.getCell(1, 1);
+ final AutofillId fieldId = field.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "xyz");
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForContextCommitted(events.get(0));
+ }
+
+ @Test
+ public void testNoUserInput() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mAfm.setUserData(new UserData.Builder("myId", "FULLY").build());
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ final EditText field = mActivity.getCell(1, 1);
+ final AutofillId fieldId = field.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Finish context.
+ mAfm.commit();
+
+ // Assert results
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForContextCommitted(events.get(0));
+ }
+
+ private String getDefaultAlgorithm() {
+ return mAfm.getDefaultFieldClassificationAlgorithm();
+ }
+
+ /*
+ * TODO(b/70407264): other scenarios:
+ *
+ * - Multipartition (for example, one response with FieldsDetection, others with datasets,
+ * saveinfo, and/or ignoredIds)
+ * - make sure detectable fields don't trigger a new partition
+ * v test partial hit (for example, 'fool' instead of 'full'
+ * v multiple fields
+ * v multiple value
+ * - combinations of above items
+ */
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
new file mode 100644
index 0000000..3382161
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -0,0 +1,1182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.Helper.assertDeprecatedClientState;
+import static android.autofillservice.cts.Helper.assertFillEventForAuthenticationSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetAuthenticationSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
+ */
+public class FillEventHistoryTest extends AutoFillServiceTestCase {
+
+ @Rule
+ public final AutofillActivityTestRule<LoginActivity> mActivityRule =
+ new AutofillActivityTestRule<LoginActivity>(LoginActivity.class);
+
+ private LoginActivity mActivity;
+
+ @Before
+ public void setActivity() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testDatasetAuthenticationSelected() throws Exception {
+ enableService();
+
+ // Set up FillResponse with dataset authentication
+ Bundle clientState = new Bundle();
+ clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+ // Prepare the authenticated response
+ final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Dataset"))
+ .build());
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username")
+ .setId("name")
+ .setPresentation(createPresentation("authentication"))
+ .setAuthentication(authentication)
+ .build())
+ .setExtras(clientState).build());
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger autofill.
+ mActivity.onUsername(View::requestFocus);
+
+ // Authenticate
+ mUiBot.selectDataset("authentication");
+ sReplier.getNextFillRequest();
+ mActivity.assertAutoFilled();
+
+ // Verify fill selection
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetAuthenticationSelected(events.get(0), "name",
+ "clientStateKey", "clientStateValue");
+ }
+
+ @Test
+ public void testAuthenticationSelected() throws Exception {
+ enableService();
+
+ // Set up FillResponse with response wide authentication
+ Bundle clientState = new Bundle();
+ clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+ // Prepare the authenticated response
+ final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+ new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username")
+ .setId("name")
+ .setPresentation(createPresentation("dataset"))
+ .build())
+ .setExtras(clientState).build());
+
+ sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
+ .setPresentation(createPresentation("authentication"))
+ .setAuthentication(authentication, ID_USERNAME)
+ .build());
+
+ // Trigger autofill.
+ mActivity.onUsername(View::requestFocus);
+
+ // Authenticate
+ mUiBot.selectDataset("authentication");
+ sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("dataset");
+
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
+ List<Event> events = selection.getEvents();
+ assertFillEventForAuthenticationSelected(events.get(0), NULL_DATASET_ID,
+ "clientStateKey", "clientStateValue");
+ }
+
+ @Test
+ public void testDatasetSelected_twoResponses() throws Exception {
+ enableService();
+
+ // Set up first partition with an anonymous dataset
+ Bundle clientState1 = new Bundle();
+ clientState1.putCharSequence("clientStateKey", "Value1");
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .setExtras(clientState1)
+ .build());
+ mActivity.expectAutoFill("username");
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ waitUntilConnected();
+ mUiBot.selectDataset("dataset1");
+ sReplier.getNextFillRequest();
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ assertDeprecatedClientState(selection, "clientStateKey", "Value1");
+ final List<Event> events = selection.getEvents();
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID,
+ "clientStateKey", "Value1");
+ }
+
+ // Set up second partition with a named dataset
+ Bundle clientState2 = new Bundle();
+ clientState2.putCharSequence("clientStateKey", "Value2");
+
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_PASSWORD, "password2")
+ .setPresentation(createPresentation("dataset2"))
+ .setId("name2")
+ .build())
+ .addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_PASSWORD, "password3")
+ .setPresentation(createPresentation("dataset3"))
+ .setId("name3")
+ .build())
+ .setExtras(clientState2)
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
+ mActivity.expectPasswordAutoFill("password3");
+
+ // Trigger autofill on password
+ mActivity.onPassword(View::requestFocus);
+ mUiBot.selectDataset("dataset3");
+ sReplier.getNextFillRequest();
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+ final List<Event> events = selection.getEvents();
+ assertFillEventForDatasetSelected(events.get(0), "name3",
+ "clientStateKey", "Value2");
+ }
+
+ mActivity.onPassword((v) -> v.setText("new password"));
+ mActivity.syncRunOnUiThread(() -> mActivity.finish());
+ waitUntilDisconnected();
+
+ {
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+ assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+
+ final List<Event> events = selection.getEvents();
+ assertFillEventForDatasetSelected(events.get(0), "name3",
+ "clientStateKey", "Value2");
+ assertFillEventForSaveShown(events.get(1), NULL_DATASET_ID,
+ "clientStateKey", "Value2");
+ }
+ }
+
+ @Test
+ public void testNoEvents_whenServiceReturnsNullResponse() throws Exception {
+ enableService();
+
+ // First reset
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("username");
+
+ mActivity.onUsername(View::requestFocus);
+ waitUntilConnected();
+ sReplier.getNextFillRequest();
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ assertNoDeprecatedClientState(selection);
+ final List<Event> events = selection.getEvents();
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // Second request
+ sReplier.addResponse(NO_RESPONSE);
+ mActivity.onPassword(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertNoDatasets();
+ waitUntilDisconnected();
+
+ InstrumentedAutoFillService.assertNoFillEventHistory();
+ }
+
+ @Test
+ public void testNoEvents_whenServiceReturnsFailure() throws Exception {
+ enableService();
+
+ // First reset
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("username");
+
+ mActivity.onUsername(View::requestFocus);
+ waitUntilConnected();
+ sReplier.getNextFillRequest();
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ assertNoDeprecatedClientState(selection);
+ final List<Event> events = selection.getEvents();
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // Second request
+ sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
+ mActivity.onPassword(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertNoDatasets();
+ waitUntilDisconnected();
+
+ InstrumentedAutoFillService.assertNoFillEventHistory();
+ }
+
+ @Test
+ public void testNoEvents_whenServiceTimesout() throws Exception {
+ enableService();
+
+ // First reset
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .build());
+ mActivity.expectAutoFill("username");
+
+ mActivity.onUsername(View::requestFocus);
+ waitUntilConnected();
+ sReplier.getNextFillRequest();
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+ assertNoDeprecatedClientState(selection);
+ final List<Event> events = selection.getEvents();
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // Second request
+ sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
+ mActivity.onPassword(View::requestFocus);
+ sReplier.getNextFillRequest();
+ waitUntilDisconnected();
+
+ InstrumentedAutoFillService.assertNoFillEventHistory();
+ }
+
+ private Bundle getBundle(String key, String value) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ /**
+ * Tests the following scenario:
+ *
+ * <ol>
+ * <li>Activity A is launched.
+ * <li>Activity A triggers autofill.
+ * <li>Activity B is launched.
+ * <li>Activity B triggers autofill.
+ * <li>User goes back to Activity A.
+ * <li>User triggers save on Activity A - at this point, service should have stats of
+ * activity B, and stats for activity A should have beeen discarded.
+ * </ol>
+ */
+ @Test
+ public void testEventsFromPreviousSessionIsDiscarded() throws Exception {
+ enableService();
+
+ // Launch activity A
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setExtras(getBundle("activity", "A"))
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .build());
+
+ // Trigger autofill on activity A
+ mActivity.onUsername(View::requestFocus);
+ waitUntilConnected();
+ sReplier.getNextFillRequest();
+
+ // Verify fill selection for Activity A
+ final FillEventHistory selectionA = InstrumentedAutoFillService.getFillEventHistory(0);
+ assertDeprecatedClientState(selectionA, "activity", "A");
+
+ // Launch activity B
+ mContext.startActivity(new Intent(mContext, CheckoutActivity.class));
+ mUiBot.assertShownByRelativeId(ID_CC_NUMBER);
+
+ // Trigger autofill on activity B
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setExtras(getBundle("activity", "B"))
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_CC_NUMBER, "4815162342")
+ .setPresentation(createPresentation("datasetB"))
+ .build())
+ .build());
+ mUiBot.focusByRelativeId(ID_CC_NUMBER);
+ sReplier.getNextFillRequest();
+
+ // Verify fill selection for Activity B
+ final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(0);
+ assertDeprecatedClientState(selectionB, "activity", "B");
+
+ // Now switch back to A...
+ mUiBot.pressBack(); // dismiss keyboard
+ mUiBot.pressBack(); // dismiss task
+ mUiBot.assertShownByRelativeId(ID_USERNAME);
+ // ...and trigger save
+ // Set credentials...
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ sReplier.getNextSaveRequest();
+
+ // Finally, make sure history is right
+ final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(0);
+ assertDeprecatedClientState(finalSelection, "activity", "B");
+ }
+
+ @Test
+ public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
+ enableService();
+
+ sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertNoDatasets();
+
+ // Trigger save
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Assert no events where generated
+ InstrumentedAutoFillService.assertNoFillEventHistory();
+ }
+
+ @Test
+ public void textContextCommitted_withoutDatasets() throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .build());
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertNoDatasets();
+
+ // Trigger save
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ sReplier.getNextSaveRequest();
+
+ // Assert it
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForSaveShown(events.get(0), NULL_DATASET_ID);
+ }
+
+ @Test
+ public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
+ enableService();
+ // Trigger 1st autofill request
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, BACKDOOR_USERNAME)
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill(BACKDOOR_USERNAME);
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+ // Verify fill history
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Trigger 2st autofill request (which will clear the fill event history)
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_PASSWORD, "whatever")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ // don't set flags
+ .build());
+ mActivity.expectPasswordAutoFill("whatever");
+ mActivity.onPassword(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.selectDataset("dataset2");
+ mActivity.assertAutoFilled();
+ // Verify fill history
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id2");
+ }
+
+ // Finish the context by login in
+ final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+
+ assertFillEventForDatasetSelected(events.get(0), "id2");
+ }
+ }
+
+ @Test
+ public void testContextCommitted_idlessDatasets() throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username1")
+ .setField(ID_PASSWORD, "password1")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username2")
+ .setField(ID_PASSWORD, "password2")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill("username1", "password1");
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+ mUiBot.selectDataset(datasetPicker, "dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // Finish the context by login in
+ mActivity.onUsername((v) -> v.setText("USERNAME"));
+ mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+ final String expectedMessage = getWelcomeMessage("USERNAME");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+ }
+
+ @Test
+ public void testContextCommitted_idlessDatasetSelected_datasetWithIdIgnored()
+ throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username1")
+ .setField(ID_PASSWORD, "password1")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_USERNAME, "username2")
+ .setField(ID_PASSWORD, "password2")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill("username1", "password1");
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+ mUiBot.selectDataset(datasetPicker, "dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // Finish the context by login in
+ mActivity.onPassword((v) -> v.setText("username1"));
+
+ final String expectedMessage = getWelcomeMessage("username1");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+
+ FillEventHistory.Event event2 = events.get(1);
+ assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event2.getDatasetId()).isNull();
+ assertThat(event2.getClientState()).isNull();
+ assertThat(event2.getSelectedDatasetIds()).isEmpty();
+ assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+ final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+ final Map<AutofillId, String> changedFields = event2.getChangedFields();
+ assertThat(changedFields).containsExactly(passwordId, "id2");
+ assertThat(event2.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ @Test
+ public void testContextCommitted_idlessDatasetIgnored_datasetWithIdSelected()
+ throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setField(ID_USERNAME, "username1")
+ .setField(ID_PASSWORD, "password1")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_USERNAME, "username2")
+ .setField(ID_PASSWORD, "password2")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill("username2", "password2");
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+ mUiBot.selectDataset(datasetPicker, "dataset2");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id2");
+ }
+
+ // Finish the context by login in
+ mActivity.onPassword((v) -> v.setText("username2"));
+
+ final String expectedMessage = getWelcomeMessage("username2");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetSelected(events.get(0), "id2");
+
+ final FillEventHistory.Event event2 = events.get(1);
+ assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event2.getDatasetId()).isNull();
+ assertThat(event2.getClientState()).isNull();
+ assertThat(event2.getSelectedDatasetIds()).containsExactly("id2");
+ assertThat(event2.getIgnoredDatasetIds()).isEmpty();
+ final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+ final Map<AutofillId, String> changedFields = event2.getChangedFields();
+ assertThat(changedFields).containsExactly(passwordId, "id2");
+ assertThat(event2.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, no dataset was selected by the user,
+ * neither the user entered values that were present in these datasets.
+ */
+ @Test
+ public void testContextCommitted_noDatasetSelected_valuesNotManuallyEntered() throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, "username1")
+ .setField(ID_PASSWORD, "password1")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_USERNAME, "username2")
+ .setField(ID_PASSWORD, "password2")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("dataset1", "dataset2");
+
+ // Verify history
+ InstrumentedAutoFillService.getFillEventHistory(0);
+
+ // Enter values not present at the datasets
+ mActivity.onUsername((v) -> v.setText("USERNAME"));
+ mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+ // Finish the context by login in
+ final String expectedMessage = getWelcomeMessage("USERNAME");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Verify history again
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ final Event event = events.get(0);
+ assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event.getDatasetId()).isNull();
+ assertThat(event.getClientState()).isNull();
+ assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+ assertThat(event.getChangedFields()).isEmpty();
+ assertThat(event.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, just one dataset was selected by the user,
+ * and the user changed the values provided by the service.
+ */
+ @Test
+ public void testContextCommitted_oneDatasetSelected() throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, "username1")
+ .setField(ID_PASSWORD, "password1")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_USERNAME, "username2")
+ .setField(ID_PASSWORD, "password2")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill("username1", "password1");
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+ mUiBot.selectDataset(datasetPicker, "dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Finish the context by login in
+ mActivity.onUsername((v) -> v.setText("USERNAME"));
+ mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+ final String expectedMessage = getWelcomeMessage("USERNAME");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+
+ final FillEventHistory.Event event2 = events.get(1);
+ assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event2.getDatasetId()).isNull();
+ assertThat(event2.getClientState()).isNull();
+ assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
+ assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+ final Map<AutofillId, String> changedFields = event2.getChangedFields();
+ final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+ final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+ assertThat(changedFields).containsExactlyEntriesIn(
+ ImmutableMap.of(usernameId, "id1", passwordId, "id1"));
+ assertThat(event2.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, both datasets were selected by the user,
+ * and the user changed the values provided by the service.
+ */
+ @Test
+ public void testContextCommitted_multipleDatasetsSelected() throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, "username")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_PASSWORD, "password")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill("username");
+
+ // Trigger autofill
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Autofill username
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Autofill password
+ mActivity.expectPasswordAutoFill("password");
+
+ mActivity.onPassword(View::requestFocus);
+ mUiBot.selectDataset("dataset2");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ assertFillEventForDatasetSelected(events.get(1), "id2");
+ }
+
+ // Finish the context by login in
+ mActivity.onPassword((v) -> v.setText("username"));
+
+ final String expectedMessage = getWelcomeMessage("username");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ assertFillEventForDatasetSelected(events.get(1), "id2");
+
+ final FillEventHistory.Event event3 = events.get(2);
+ assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event3.getDatasetId()).isNull();
+ assertThat(event3.getClientState()).isNull();
+ assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+ assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+ final Map<AutofillId, String> changedFields = event3.getChangedFields();
+ final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+ assertThat(changedFields).containsExactly(passwordId, "id2");
+ assertThat(event3.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, both datasets were selected by the user,
+ * and the user didn't change the values provided by the service.
+ */
+ @Test
+ public void testContextCommitted_multipleDatasetsSelected_butNotChanged() throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, BACKDOOR_USERNAME)
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_PASSWORD, "whatever")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill(BACKDOOR_USERNAME);
+
+ // Trigger autofill
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Autofill username
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Autofill password
+ mActivity.expectPasswordAutoFill("whatever");
+
+ mActivity.onPassword(View::requestFocus);
+ mUiBot.selectDataset("dataset2");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ assertFillEventForDatasetSelected(events.get(1), "id2");
+ }
+
+ // Finish the context by login in
+ final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ {
+ // Verify fill history
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ assertFillEventForDatasetSelected(events.get(1), "id2");
+
+ final FillEventHistory.Event event3 = events.get(2);
+ assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event3.getDatasetId()).isNull();
+ assertThat(event3.getClientState()).isNull();
+ assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+ assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+ assertThat(event3.getChangedFields()).isEmpty();
+ assertThat(event3.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, the user selected the dataset, than changed
+ * the autofilled values, but then change the values again so they match what was provided by
+ * the service.
+ */
+ @Test
+ public void testContextCommitted_oneDatasetSelected_Changed_thenChangedBack()
+ throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, "username")
+ .setField(ID_PASSWORD, "username")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ mActivity.expectAutoFill("username", "username");
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ mUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Change the fields to different values from datasets
+ mActivity.onUsername((v) -> v.setText("USERNAME"));
+ mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+ // Then change back to dataset values
+ mActivity.onUsername((v) -> v.setText("username"));
+ mActivity.onPassword((v) -> v.setText("username"));
+
+ final String expectedMessage = getWelcomeMessage("username");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+
+ FillEventHistory.Event event2 = events.get(1);
+ assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event2.getDatasetId()).isNull();
+ assertThat(event2.getClientState()).isNull();
+ assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
+ assertThat(event2.getIgnoredDatasetIds()).isEmpty();
+ assertThat(event2.getChangedFields()).isEmpty();
+ assertThat(event2.getManuallyEnteredField()).isEmpty();
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, the user did not selected any dataset, but
+ * the user manually entered values that match what was provided by the service.
+ */
+ @Test
+ public void testContextCommitted_noDatasetSelected_butManuallyEntered()
+ throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, BACKDOOR_USERNAME)
+ .setField(ID_PASSWORD, "NotUsedPassword")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_USERNAME, "NotUserUsername")
+ .setField(ID_PASSWORD, "whatever")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("dataset1", "dataset2");
+
+ // Verify history
+ InstrumentedAutoFillService.getFillEventHistory(0);
+
+ // Enter values present at the datasets
+ mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+ mActivity.onPassword((v) -> v.setText("whatever"));
+
+ // Finish the context by login in
+ final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Verify history
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+ FillEventHistory.Event event = events.get(0);
+ assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event.getDatasetId()).isNull();
+ assertThat(event.getClientState()).isNull();
+ assertThat(event.getSelectedDatasetIds()).isEmpty();
+ assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+ assertThat(event.getChangedFields()).isEmpty();
+ final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+ final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+
+ final Map<AutofillId, Set<String>> manuallyEnteredFields =
+ event.getManuallyEnteredField();
+ assertThat(manuallyEnteredFields).isNotNull();
+ assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+ assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1");
+ assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2");
+ }
+ }
+
+ /**
+ * Tests scenario where the context was committed, the user did not selected any dataset, but
+ * the user manually entered values that match what was provided by the service on different
+ * datasets.
+ */
+ @Test
+ public void testContextCommitted_noDatasetSelected_butManuallyEntered_matchingMultipleDatasets()
+ throws Exception {
+ enableService();
+
+ sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+ new CannedDataset.Builder()
+ .setId("id1")
+ .setField(ID_USERNAME, BACKDOOR_USERNAME)
+ .setField(ID_PASSWORD, "NotUsedPassword")
+ .setPresentation(createPresentation("dataset1"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id2")
+ .setField(ID_USERNAME, "NotUserUsername")
+ .setField(ID_PASSWORD, "whatever")
+ .setPresentation(createPresentation("dataset2"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("id3")
+ .setField(ID_USERNAME, BACKDOOR_USERNAME)
+ .setField(ID_PASSWORD, "whatever")
+ .setPresentation(createPresentation("dataset3"))
+ .build())
+ .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+ .build());
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
+
+ // Verify history
+ InstrumentedAutoFillService.getFillEventHistory(0);
+
+ // Enter values present at the datasets
+ mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+ mActivity.onPassword((v) -> v.setText("whatever"));
+
+ // Finish the context by login in
+ final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Verify history
+ {
+ final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+
+ final FillEventHistory.Event event = events.get(0);
+ assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+ assertThat(event.getDatasetId()).isNull();
+ assertThat(event.getClientState()).isNull();
+ assertThat(event.getSelectedDatasetIds()).isEmpty();
+ assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2", "id3");
+ assertThat(event.getChangedFields()).isEmpty();
+ final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+ final AutofillId passwordId = mActivity.getPassword().getAutofillId();
+
+ final Map<AutofillId, Set<String>> manuallyEnteredFields =
+ event.getManuallyEnteredField();
+ assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+ assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1", "id3");
+ assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2", "id3");
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
new file mode 100644
index 0000000..67771f4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.service.autofill.FillResponse.FLAG_DISABLE_ACTIVITY_ONLY;
+import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class FillResponseTest {
+
+ private final AutofillId mAutofillId = new AutofillId(42);
+ private final FillResponse.Builder mBuilder = new FillResponse.Builder();
+ private final AutofillId[] mIds = new AutofillId[] { mAutofillId };
+ private final SaveInfo mSaveInfo = new SaveInfo.Builder(0, mIds).build();
+ private final Bundle mClientState = new Bundle();
+ private final Dataset mDataset = new Dataset.Builder()
+ .setValue(mAutofillId, AutofillValue.forText("forty-two"))
+ .build();
+ private final long mDisableDuration = 666;
+ @Mock private RemoteViews mPresentation;
+ @Mock private RemoteViews mHeader;
+ @Mock private RemoteViews mFooter;
+ @Mock private IntentSender mIntentSender;
+
+ @Test
+ public void testBuilder_setAuthentication_invalid() {
+ // null ids
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.setAuthentication(null, mIntentSender, mPresentation));
+ // empty ids
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.setAuthentication(new AutofillId[] {}, mIntentSender,
+ mPresentation));
+ // null intent sender
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.setAuthentication(mIds, null, mPresentation));
+ // null presentation
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.setAuthentication(mIds, mIntentSender, null));
+ }
+
+ @Test
+ public void testBuilder_setAuthentication_valid() {
+ new FillResponse.Builder().setAuthentication(mIds, null, null);
+ new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+ }
+
+ @Test
+ public void testBuilder_setAuthentication_illegalState() {
+ assertThrows(IllegalStateException.class,
+ () -> new FillResponse.Builder().setHeader(mHeader).setAuthentication(mIds,
+ mIntentSender, mPresentation));
+ assertThrows(IllegalStateException.class,
+ () -> new FillResponse.Builder().setFooter(mFooter).setAuthentication(mIds,
+ mIntentSender, mPresentation));
+ }
+
+ @Test
+ public void testBuilder_setHeaderOrFooterInvalid() {
+ assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setHeader(null));
+ assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setFooter(null));
+ }
+
+ @Test
+ public void testBuilder_setHeaderOrFooterAfterAuthentication() {
+ FillResponse.Builder builder =
+ new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+ assertThrows(IllegalStateException.class, () -> builder.setHeader(mHeader));
+ assertThrows(IllegalStateException.class, () -> builder.setHeader(mFooter));
+ }
+
+ @Test
+ public void testBuilder_setFlag_invalid() {
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
+ }
+
+ @Test
+ public void testBuilder_setFlag_valid() {
+ mBuilder.setFlags(0);
+ mBuilder.setFlags(FLAG_TRACK_CONTEXT_COMMITED);
+ mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY);
+ }
+
+ @Test
+ public void testBuilder_disableAutofill_invalid() {
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(0));
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(-1));
+ }
+
+ @Test
+ public void testBuilder_disableAutofill_valid() {
+ mBuilder.disableAutofill(mDisableDuration);
+ mBuilder.disableAutofill(Long.MAX_VALUE);
+ }
+
+ @Test
+ public void testBuilder_disableAutofill_mustBeTheOnlyMethodCalled() {
+ // No method can be called after disableAutofill()
+ mBuilder.disableAutofill(mDisableDuration);
+ assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+ assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(mDataset));
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation));
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.setFieldClassificationIds(mAutofillId));
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.setClientState(mClientState));
+
+ // And vice-versa...
+ final FillResponse.Builder builder1 = new FillResponse.Builder().setSaveInfo(mSaveInfo);
+ assertThrows(IllegalStateException.class, () -> builder1.disableAutofill(mDisableDuration));
+ final FillResponse.Builder builder2 = new FillResponse.Builder().addDataset(mDataset);
+ assertThrows(IllegalStateException.class, () -> builder2.disableAutofill(mDisableDuration));
+ final FillResponse.Builder builder3 =
+ new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+ assertThrows(IllegalStateException.class, () -> builder3.disableAutofill(mDisableDuration));
+ final FillResponse.Builder builder4 =
+ new FillResponse.Builder().setFieldClassificationIds(mAutofillId);
+ assertThrows(IllegalStateException.class, () -> builder4.disableAutofill(mDisableDuration));
+ final FillResponse.Builder builder5 =
+ new FillResponse.Builder().setClientState(mClientState);
+ assertThrows(IllegalStateException.class, () -> builder5.disableAutofill(mDisableDuration));
+ }
+
+ @Test
+ public void testBuilder_setFieldClassificationIds_invalid() {
+ assertThrows(NullPointerException.class,
+ () -> mBuilder.setFieldClassificationIds((AutofillId) null));
+ assertThrows(NullPointerException.class,
+ () -> mBuilder.setFieldClassificationIds((AutofillId[]) null));
+ final AutofillId[] oneTooMany =
+ new AutofillId[UserData.getMaxFieldClassificationIdsSize() + 1];
+ for (int i = 0; i < oneTooMany.length; i++) {
+ oneTooMany[i] = new AutofillId(i);
+ }
+ assertThrows(IllegalArgumentException.class,
+ () -> mBuilder.setFieldClassificationIds(oneTooMany));
+ }
+
+ @Test
+ public void testBuilder_setFieldClassificationIds_valid() {
+ mBuilder.setFieldClassificationIds(mAutofillId);
+ }
+
+ @Test
+ public void testBuilder_setFieldClassificationIds_setsFlag() {
+ mBuilder.setFieldClassificationIds(mAutofillId);
+ assertThat(mBuilder.build().getFlags()).isEqualTo(FLAG_TRACK_CONTEXT_COMMITED);
+ }
+
+ @Test
+ public void testBuilder_setFieldClassificationIds_addsFlag() {
+ mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY).setFieldClassificationIds(mAutofillId);
+ assertThat(mBuilder.build().getFlags())
+ .isEqualTo(FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+ }
+
+ @Test
+ public void testBuild_invalid() {
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+ }
+
+ @Test
+ public void testBuild_valid() {
+ // authentication only
+ assertThat(new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation)
+ .build()).isNotNull();
+ // save info only
+ assertThat(new FillResponse.Builder().setSaveInfo(mSaveInfo).build()).isNotNull();
+ // dataset only
+ assertThat(new FillResponse.Builder().addDataset(mDataset).build()).isNotNull();
+ // disable autofill only
+ assertThat(new FillResponse.Builder().disableAutofill(mDisableDuration).build())
+ .isNotNull();
+ // fill detection only
+ assertThat(new FillResponse.Builder().setFieldClassificationIds(mAutofillId).build())
+ .isNotNull();
+ // client state only
+ assertThat(new FillResponse.Builder().setClientState(mClientState).build())
+ .isNotNull();
+ }
+
+ @Test
+ public void testBuilder_build_headerOrFooterWithoutDatasets() {
+ assertThrows(IllegalStateException.class,
+ () -> new FillResponse.Builder().setHeader(mHeader).build());
+ assertThrows(IllegalStateException.class,
+ () -> new FillResponse.Builder().setFooter(mFooter).build());
+ }
+
+ @Test
+ public void testNoMoreInteractionsAfterBuild() {
+ assertThat(mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build())
+ .isNotNull();
+
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build());
+ assertThrows(IllegalStateException.class, () -> mBuilder.setIgnoredIds(mIds));
+ assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(null));
+ assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+ assertThrows(IllegalStateException.class, () -> mBuilder.setClientState(mClientState));
+ assertThrows(IllegalStateException.class, () -> mBuilder.setFlags(0));
+ assertThrows(IllegalStateException.class,
+ () -> mBuilder.setFieldClassificationIds(mAutofillId));
+ assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
+ assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
index 7be4496..2e84a32 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
@@ -71,10 +71,10 @@
}
public boolean waitUntilResumed() throws InterruptedException {
- return mResumed.await(Helper.UI_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
}
public boolean waitUntilStopped() throws InterruptedException {
- return mStopped.await(Helper.UI_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return mStopped.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
index 33321c7..29af4a2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
@@ -83,7 +83,7 @@
getSystemService(AutofillManager.class).cancel();
}
- private EditText getCell(int row, int column) {
+ EditText getCell(int row, int column) {
return mCells[row - 1][column - 1];
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index f1d02dd..741c029 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -18,8 +18,14 @@
import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
import static android.autofillservice.cts.UiBot.PORTRAIT;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -27,10 +33,16 @@
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
+import android.autofillservice.cts.common.SettingsHelper;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.icu.util.Calendar;
+import android.os.Bundle;
+import android.service.autofill.FieldClassification;
+import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
@@ -43,10 +55,10 @@
import android.view.autofill.AutofillValue;
import android.webkit.WebView;
-import com.android.compatibility.common.util.SystemUtil;
-
import java.lang.reflect.Field;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.function.Function;
/**
@@ -54,7 +66,7 @@
*/
final class Helper {
- private static final String TAG = "AutoFillCtsHelper";
+ static final String TAG = "AutoFillCtsHelper";
static final boolean VERBOSE = false;
@@ -64,54 +76,18 @@
static final String ID_PASSWORD = "password";
static final String ID_LOGIN = "login";
static final String ID_OUTPUT = "output";
+ static final String ID_STATIC_TEXT = "static_text";
+
+ public static final String NULL_DATASET_ID = null;
+
+ /**
+ * Can be used in cases where the autofill values is required by irrelevant (like adding a
+ * value to an authenticated dataset).
+ */
+ public static final String UNUSED_AUTOFILL_VALUE = null;
private static final String CMD_LIST_SESSIONS = "cmd autofill list sessions";
- /**
- * Timeout (in milliseconds) until framework binds / unbinds from service.
- */
- static final long CONNECTION_TIMEOUT_MS = 2000;
-
- /**
- * Timeout (in milliseconds) until framework unbinds from a service.
- */
- static final long IDLE_UNBIND_TIMEOUT_MS = 5000;
-
- /**
- * Timeout (in milliseconds) for expected auto-fill requests.
- */
- static final long FILL_TIMEOUT_MS = 2000;
-
- /**
- * Timeout (in milliseconds) for expected save requests.
- */
- static final long SAVE_TIMEOUT_MS = 5000;
-
- /**
- * Time to wait if a UI change is not expected
- */
- static final long NOT_SHOWING_TIMEOUT_MS = 500;
-
- /**
- * Timeout (in milliseconds) for UI operations. Typically used by {@link UiBot}.
- */
- static final int UI_TIMEOUT_MS = 2000;
-
- /**
- * Timeout (in milliseconds) for an activity to be brought out to top.
- */
- static final int ACTIVITY_RESURRECTION_MS = 5000;
-
- /**
- * Timeout (in milliseconds) for changing the screen orientation.
- */
- static final int UI_SCREEN_ORIENTATION_TIMEOUT_MS = 5000;
-
- /**
- * Time to wait in between retries
- */
- static final int RETRY_MS = 100;
-
private final static String ACCELLEROMETER_CHANGE =
"content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
+ "--bind value:i:%d";
@@ -134,74 +110,24 @@
return id.equals(getHtmlName(node));
};
+ private static final NodeFilter HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
+ return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
+ };
+
private static final NodeFilter TEXT_FILTER = (node, id) -> {
return id.equals(node.getText());
};
- private static final NodeFilter WEBVIEW_ROOT_FILTER = (node, id) -> {
- // TODO(b/66953802): class name should be android.webkit.WebView, and form name should be
- // inside HtmlInfo, but Chromium 61 does not implement that.
+ private static final NodeFilter WEBVIEW_FORM_FILTER = (node, id) -> {
final String className = node.getClassName();
- final String formName;
- if (className.equals("android.webkit.WebView")) {
- final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
- formName = getAttributeValue(htmlInfo, "name");
- } else {
- formName = className;
- }
+ if (!className.equals("android.webkit.WebView")) return false;
+
+ final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
+ final String formName = getAttributeValue(htmlInfo, "name");
return id.equals(formName);
};
/**
- * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the
- * {@link #UI_TIMEOUT_MS} is reached.
- */
- static void eventually(Runnable r) throws Exception {
- eventually(r, UI_TIMEOUT_MS);
- }
-
- /**
- * Runs a {@code r}, ignoring all {@link RuntimeException} and {@link Error} until the
- * {@code timeout} is reached.
- */
- static void eventually(Runnable r, int timeout) throws Exception {
- long startTime = System.currentTimeMillis();
-
- while (true) {
- try {
- r.run();
- break;
- } catch (RuntimeException | Error e) {
- if (System.currentTimeMillis() - startTime < timeout) {
- if (VERBOSE) Log.v(TAG, "Ignoring", e);
- Thread.sleep(RETRY_MS);
- } else {
- if (e instanceof RetryableException) {
- throw e;
- } else {
- throw new RetryableException(e, "Timedout out after %d ms", timeout);
- }
- }
- }
- }
- }
-
- /**
- * Runs a Shell command, returning a trimmed response.
- */
- static String runShellCommand(String template, Object...args) {
- final String command = String.format(template, args);
- Log.d(TAG, "runShellCommand(): " + command);
- try {
- final String result = SystemUtil
- .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
- return TextUtils.isEmpty(result) ? "" : result.trim();
- } catch (Exception e) {
- throw new RuntimeException("Command '" + command + "' failed: ", e);
- }
- }
-
- /**
* Dump the assist structure on logcat.
*/
static void dumpStructure(String message, AssistStructure structure) {
@@ -236,32 +162,7 @@
* Sets whether the user completed the initial setup.
*/
static void setUserComplete(Context context, boolean complete) {
- if (isUserComplete() == complete) return;
-
- final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
- USER_SETUP_COMPLETE);
- final String newValue = complete ? "1" : null;
- runShellCommand("settings put secure %s %s default", USER_SETUP_COMPLETE, newValue);
- observer.assertCalled();
-
- assertIsUserComplete(complete);
- }
-
- /**
- * Gets whether the user completed the initial setup.
- */
- static boolean isUserComplete() {
- final String isIt = runShellCommand("settings get secure %s", USER_SETUP_COMPLETE);
- return "1".equals(isIt);
- }
-
- /**
- * Assets that user completed (or not) the initial setup.
- */
- static void assertIsUserComplete(boolean expected) {
- final boolean actual = isUserComplete();
- assertWithMessage("Invalid value for secure setting %s", USER_SETUP_COMPLETE)
- .that(actual).isEqualTo(expected);
+ SettingsHelper.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
}
private static void dump(StringBuffer buffer, ViewNode node, String prefix, int childId) {
@@ -391,6 +292,14 @@
}
/**
+ * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
+ * not found.
+ */
+ static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
+ return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
+ }
+
+ /**
* Gets the {@code name} attribute of a node representing an HTML input tag.
*/
@Nullable
@@ -428,7 +337,7 @@
}
/**
- * Asserts a text-base node is sanitized.
+ * Asserts a text-based node is sanitized.
*/
static void assertTextIsSanitized(ViewNode node) {
final CharSequence text = node.getText();
@@ -436,9 +345,15 @@
if (!TextUtils.isEmpty(text)) {
throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
}
+
+ assertNotFromResources(node);
assertNodeHasNoAutofillValue(node);
}
+ private static void assertNotFromResources(ViewNode node) {
+ assertThat(node.getTextIdEntry()).isNull();
+ }
+
static void assertNodeHasNoAutofillValue(ViewNode node) {
final AutofillValue value = node.getAutofillValue();
if (value != null) {
@@ -449,22 +364,31 @@
/**
* Asserts the contents of a text-based node that is also auto-fillable.
- *
*/
static void assertTextOnly(ViewNode node, String expectedValue) {
assertText(node, expectedValue, false);
+ assertNotFromResources(node);
}
/**
* Asserts the contents of a text-based node that is also auto-fillable.
- *
*/
- static void assertTextAndValue(ViewNode node, String expectedValue) {
- assertText(node, expectedValue, true);
+ static void assertTextOnly(AssistStructure structure, String resourceId, String expectedValue) {
+ final ViewNode node = findNodeByResourceId(structure, resourceId);
+ assertText(node, expectedValue, false);
+ assertNotFromResources(node);
}
/**
- * Asserts a text-base node exists and verify its values.
+ * Asserts the contents of a text-based node that is also auto-fillable.
+ */
+ static void assertTextAndValue(ViewNode node, String expectedValue) {
+ assertText(node, expectedValue, true);
+ assertNotFromResources(node);
+ }
+
+ /**
+ * Asserts a text-based node exists and verify its values.
*/
static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
String expectedValue) {
@@ -474,7 +398,7 @@
}
/**
- * Asserts a text-base node exists and is sanitized.
+ * Asserts a text-based node exists and is sanitized.
*/
static ViewNode assertValue(AssistStructure structure, String resourceId,
String expectedValue) {
@@ -483,16 +407,28 @@
return node;
}
+ /**
+ * Asserts the values of a text-based node whose string come from resoruces.
+ */
+ static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
+ String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
+ final ViewNode node = findNodeByResourceId(structure, resourceId);
+ assertText(node, expectedValue, isAutofillable);
+ assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
+ return node;
+ }
+
private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
- assertWithMessage("wrong text on %s", node).that(node.getText().toString())
+ assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
.isEqualTo(expectedValue);
final AutofillValue value = node.getAutofillValue();
+ final AutofillId id = node.getAutofillId();
if (isAutofillable) {
- assertWithMessage("null auto-fill value on %s", node).that(value).isNotNull();
- assertWithMessage("wrong auto-fill value on %s", node)
+ assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
+ assertWithMessage("wrong auto-fill value on %s", id)
.that(value.getTextValue().toString()).isEqualTo(expectedValue);
} else {
- assertWithMessage("node %s should not have AutofillValue", node).that(value).isNull();
+ assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
}
}
@@ -501,9 +437,10 @@
*/
static ViewNode assertTextValue(ViewNode node, String expectedText) {
final AutofillValue value = node.getAutofillValue();
- assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
- assertWithMessage("wrong autofill type on %s", node).that(value.isText()).isTrue();
- assertWithMessage("wrong autofill value on %s", node).that(value.getTextValue().toString())
+ final AutofillId id = node.getAutofillId();
+ assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+ assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
+ assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
.isEqualTo(expectedText);
return node;
}
@@ -513,9 +450,10 @@
*/
static ViewNode assertListValue(ViewNode node, int expectedIndex) {
final AutofillValue value = node.getAutofillValue();
- assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
- assertWithMessage("wrong autofill type on %s", node).that(value.isList()).isTrue();
- assertWithMessage("wrong autofill value on %s", node).that(value.getListValue())
+ final AutofillId id = node.getAutofillId();
+ assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+ assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
+ assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
.isEqualTo(expectedIndex);
return node;
}
@@ -525,9 +463,10 @@
*/
static void assertToggleValue(ViewNode node, boolean expectedToggle) {
final AutofillValue value = node.getAutofillValue();
- assertWithMessage("null autofill value on %s", node).that(value).isNotNull();
- assertWithMessage("wrong autofill type on %s", node).that(value.isToggle()).isTrue();
- assertWithMessage("wrong autofill value on %s", node).that(value.getToggleValue())
+ final AutofillId id = node.getAutofillId();
+ assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+ assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
+ assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
.isEqualTo(expectedToggle);
}
@@ -594,7 +533,7 @@
}
/**
- * Asserts a text-base node exists and is sanitized.
+ * Asserts a text-based node exists and is sanitized.
*/
static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
final ViewNode node = findNodeByResourceId(structure, resourceId);
@@ -701,7 +640,7 @@
/**
* Prevents the screen to rotate by itself
*/
- public static void disableAutoRotation(UiBot uiBot) {
+ public static void disableAutoRotation(UiBot uiBot) throws Exception {
runShellCommand(ACCELLEROMETER_CHANGE, 0);
uiBot.setScreenOrientation(PORTRAIT);
}
@@ -718,28 +657,21 @@
*
* @return The pid of the process
*/
- public static int getOutOfProcessPid(@NonNull String processName) {
- long startTime = System.currentTimeMillis();
+ public static int getOutOfProcessPid(@NonNull String processName, @NonNull Timeout timeout)
+ throws Exception {
- while (System.currentTimeMillis() - startTime <= UI_TIMEOUT_MS) {
- String[] allProcessDescs = runShellCommand("ps -eo PID,ARGS=CMD").split("\n");
+ return timeout.run("getOutOfProcessPid(" + processName + ")", () -> {
+ final String[] allProcessDescs = runShellCommand("ps -eo PID,ARGS=CMD").split("\n");
for (String processDesc : allProcessDescs) {
- String[] pidAndName = processDesc.trim().split(" ");
+ final String[] pidAndName = processDesc.trim().split(" ");
if (pidAndName[1].equals(processName)) {
return Integer.parseInt(pidAndName[0]);
}
}
-
- try {
- Thread.sleep(RETRY_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- throw new IllegalStateException("process not found");
+ return null;
+ });
}
/**
@@ -782,43 +714,37 @@
* Uses Settings to enable the given autofill service for the default user, and checks the
* value was properly check, throwing an exception if it was not.
*/
- public static void enableAutofillService(Context context, String serviceName) {
+ public static void enableAutofillService(@NonNull Context context,
+ @NonNull String serviceName) {
if (isAutofillServiceEnabled(serviceName)) return;
- final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
- AUTOFILL_SERVICE);
- runShellCommand("settings put secure %s %s default", AUTOFILL_SERVICE, serviceName);
- observer.assertCalled();
- assertAutofillServiceStatus(serviceName, true);
+ SettingsHelper.syncSet(context, AUTOFILL_SERVICE, serviceName);
}
/**
* Uses Settings to disable the given autofill service for the default user, and checks the
* value was properly check, throwing an exception if it was not.
*/
- public static void disableAutofillService(Context context, String serviceName) {
+ public static void disableAutofillService(@NonNull Context context,
+ @NonNull String serviceName) {
if (!isAutofillServiceEnabled(serviceName)) return;
- final OneTimeSettingsListener observer = new OneTimeSettingsListener(context,
- AUTOFILL_SERVICE);
- runShellCommand("settings delete secure %s", AUTOFILL_SERVICE);
- observer.assertCalled();
- assertAutofillServiceStatus(serviceName, false);
+ SettingsHelper.syncDelete(context, AUTOFILL_SERVICE);
}
/**
* Checks whether the given service is set as the autofill service for the default user.
*/
- public static boolean isAutofillServiceEnabled(String serviceName) {
- final String actualName = runShellCommand("settings get secure %s", AUTOFILL_SERVICE);
+ private static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
+ final String actualName = SettingsHelper.get(AUTOFILL_SERVICE);
return serviceName.equals(actualName);
}
/**
* Asserts whether the given service is enabled as the autofill service for the default user.
*/
- public static void assertAutofillServiceStatus(String serviceName, boolean enabled) {
- final String actual = runShellCommand("settings get secure %s", AUTOFILL_SERVICE);
+ public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
+ final String actual = SettingsHelper.get(AUTOFILL_SERVICE);
final String expected = enabled ? serviceName : "null";
assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
.that(actual).isEqualTo(expected);
@@ -828,11 +754,15 @@
* Asserts that there is no session left in the service.
*/
public static void assertNoDanglingSessions() {
- final String result = runShellCommand(CMD_LIST_SESSIONS);
+ final String result = listSessions();
assertWithMessage("Dangling sessions ('%s'): %s'", CMD_LIST_SESSIONS, result).that(result)
.isEmpty();
}
+ public static String listSessions() {
+ return runShellCommand(CMD_LIST_SESSIONS);
+ }
+
/**
* Asserts that there is a pending session for the given package.
*/
@@ -952,17 +882,267 @@
/**
* Finds a {@link WebView} node given its expected form name.
*/
- public static ViewNode findWebViewNode(AssistStructure structure, String formName) {
- return findNodeByFilter(structure, formName, WEBVIEW_ROOT_FILTER);
+ public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
+ return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
+ }
+
+ private static void assertClientState(Object container, Bundle clientState,
+ String key, String value) {
+ assertWithMessage("'%s' should have client state", container)
+ .that(clientState).isNotNull();
+ assertWithMessage("Wrong number of client state extras on '%s'", container)
+ .that(clientState.keySet().size()).isEqualTo(1);
+ assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
+ .that(clientState.getString(key)).isEqualTo(value);
}
/**
- * Finds a {@link WebView} node given its expected form name.
+ * Asserts the content of a {@link FillEventHistory#getClientState()}.
+ *
+ * @param history event to be asserted
+ * @param key the only key expected in the client state bundle
+ * @param value the only value expected in the client state bundle
*/
- public static ViewNode findWebViewNode(ViewNode node, String formName) {
- return findNodeByFilter(node, formName, WEBVIEW_ROOT_FILTER);
+ @SuppressWarnings("javadoc")
+ public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
+ @NonNull String key, @NonNull String value) {
+ assertThat(history).isNotNull();
+ @SuppressWarnings("deprecation")
+ final Bundle clientState = history.getClientState();
+ assertClientState(history, clientState, key, value);
+ }
+
+ /**
+ * Asserts the {@link FillEventHistory#getClientState()} is not set.
+ *
+ * @param history event to be asserted
+ */
+ @SuppressWarnings("javadoc")
+ public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
+ assertThat(history).isNotNull();
+ @SuppressWarnings("deprecation")
+ final Bundle clientState = history.getClientState();
+ assertWithMessage("History '%s' should not have client state", history)
+ .that(clientState).isNull();
+ }
+
+ /**
+ * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
+ *
+ * @param event event to be asserted
+ * @param eventType expected type
+ * @param datasetId dataset set id expected in the event
+ * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
+ * have client state)
+ * @param value the only value expected in the client state bundle (or {@code null} if it
+ * shouldn't have client state)
+ * @param fieldClassificationResults expected results when asserting field classification
+ */
+ private static void assertFillEvent(@NonNull FillEventHistory.Event event,
+ int eventType, @Nullable String datasetId,
+ @Nullable String key, @Nullable String value,
+ @Nullable FieldClassificationResult[] fieldClassificationResults) {
+ assertThat(event).isNotNull();
+ assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
+ if (datasetId == null) {
+ assertWithMessage("Event %s should not have dataset id", event)
+ .that(event.getDatasetId()).isNull();
+ } else {
+ assertWithMessage("Wrong dataset id for %s", event)
+ .that(event.getDatasetId()).isEqualTo(datasetId);
+ }
+ final Bundle clientState = event.getClientState();
+ if (key == null) {
+ assertWithMessage("Event '%s' should not have client state", event)
+ .that(clientState).isNull();
+ } else {
+ assertClientState(event, clientState, key, value);
+ }
+ assertWithMessage("Event '%s' should not have selected datasets", event)
+ .that(event.getSelectedDatasetIds()).isEmpty();
+ assertWithMessage("Event '%s' should not have ignored datasets", event)
+ .that(event.getIgnoredDatasetIds()).isEmpty();
+ assertWithMessage("Event '%s' should not have changed fields", event)
+ .that(event.getChangedFields()).isEmpty();
+ assertWithMessage("Event '%s' should not have manually-entered fields", event)
+ .that(event.getManuallyEnteredField()).isEmpty();
+ final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
+ if (fieldClassificationResults == null) {
+ assertThat(detectedFields).isEmpty();
+ } else {
+ assertThat(detectedFields).hasSize(fieldClassificationResults.length);
+ int i = 0;
+ for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
+ assertMatches(i, entry, fieldClassificationResults[i]);
+ i++;
+ }
+ }
+ }
+
+ private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
+ FieldClassificationResult expectedResult) {
+ assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
+ .isEqualTo(expectedResult.id);
+ final List<Match> matches = actualResult.getValue().getMatches();
+ assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
+ .isEqualTo(expectedResult.remoteIds.length);
+ for (int j = 0; j < matches.size(); j++) {
+ final Match match = matches.get(j);
+ assertWithMessage("Wrong remoteId at (%s, %s): %s", i, j, match)
+ .that(match.getRemoteId()).isEqualTo(expectedResult.remoteIds[j]);
+ assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
+ .that(match.getScore()).isWithin(expectedResult.scores[j]);
+ assertWithMessage("Wrong algorithm at (%s, %s): %s", i, j, match)
+ .that(match.getAlgorithm()).isEqualTo(expectedResult.algorithms[j]);
+ }
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+ *
+ * @param event event to be asserted
+ * @param datasetId dataset set id expected in the event
+ */
+ public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+ @Nullable String datasetId) {
+ assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+ *
+ * @param event event to be asserted
+ * @param datasetId dataset set id expected in the event
+ * @param key the only key expected in the client state bundle
+ * @param value the only value expected in the client state bundle
+ */
+ public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+ @Nullable String datasetId, @Nullable String key, @Nullable String value) {
+ assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+ *
+ * @param event event to be asserted
+ * @param datasetId dataset set id expected in the event
+ * @param key the only key expected in the client state bundle
+ * @param value the only value expected in the client state bundle
+ */
+ public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+ @NonNull String datasetId, @NonNull String key, @NonNull String value) {
+ assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+ *
+ * @param event event to be asserted
+ * @param datasetId dataset set id expected in the event
+ */
+ public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+ @NonNull String datasetId) {
+ assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
+ * event.
+ *
+ * @param event event to be asserted
+ * @param datasetId dataset set id expected in the event
+ * @param key the only key expected in the client state bundle
+ * @param value the only value expected in the client state bundle
+ */
+ public static void assertFillEventForDatasetAuthenticationSelected(
+ @NonNull FillEventHistory.Event event,
+ @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+ assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+ }
+
+ /**
+ * Asserts the content of a
+ * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
+ *
+ * @param event event to be asserted
+ * @param datasetId dataset set id expected in the event
+ * @param key the only key expected in the client state bundle
+ * @param value the only value expected in the client state bundle
+ */
+ public static void assertFillEventForAuthenticationSelected(
+ @NonNull FillEventHistory.Event event,
+ @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+ assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+ }
+
+ public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+ @NonNull AutofillId fieldId, @NonNull String remoteId, float score,
+ @NonNull String algorithm) {
+ assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
+ new FieldClassificationResult[] {
+ new FieldClassificationResult(fieldId, remoteId, score, algorithm)
+ });
+ }
+
+ public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+ @NonNull FieldClassificationResult[] results) {
+ assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
+ }
+
+ public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
+ assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
+ }
+
+ @NonNull
+ public static String getActivityName(List<FillContext> contexts) {
+ if (contexts == null) return "N/A (null contexts)";
+
+ if (contexts.isEmpty()) return "N/A (empty contexts)";
+
+ final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
+ if (structure == null) return "N/A (no AssistStructure)";
+
+ final ComponentName componentName = structure.getActivityComponent();
+ if (componentName == null) return "N/A (no component name)";
+
+ return componentName.flattenToShortString();
+ }
+
+ public static void assertFloat(float actualValue, float expectedValue) {
+ assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
+ }
+
+ public static void assertHasFlags(int actualFlags, int expectedFlags) {
+ assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
+ .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
}
private Helper() {
+ throw new UnsupportedOperationException("contain static methods only");
+ }
+
+ static class FieldClassificationResult {
+ public final AutofillId id;
+ public final String[] remoteIds;
+ public final float[] scores;
+ public final String[] algorithms;
+
+ FieldClassificationResult(@NonNull AutofillId id, @NonNull String remoteId, float score,
+ String algorithm) {
+ this(id, new String[] { remoteId }, new float[] { score }, new String[] { algorithm });
+ }
+
+ FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] remoteIds,
+ float[] scores, String[] algorithms) {
+ this.id = id;
+ this.remoteIds = remoteIds;
+ this.scores = scores;
+ this.algorithms = algorithms;
+ }
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java b/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
index 01878ab..66e857b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
@@ -21,5 +21,6 @@
*/
enum IdMode {
RESOURCE_ID,
- HTML_NAME
+ HTML_NAME,
+ HTML_NAME_OR_RESOURCE_ID
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
index e96df05..935be23 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
@@ -39,30 +39,35 @@
public class ImageTransformationTest {
@Test
+ @SuppressWarnings("deprecation")
public void testAllNullBuilder() {
assertThrows(NullPointerException.class,
() -> new ImageTransformation.Builder(null, null, 0));
}
@Test
+ @SuppressWarnings("deprecation")
public void testNullAutofillIdBuilder() {
assertThrows(NullPointerException.class,
() -> new ImageTransformation.Builder(null, Pattern.compile(""), 1));
}
@Test
+ @SuppressWarnings("deprecation")
public void testNullRegexBuilder() {
assertThrows(NullPointerException.class,
() -> new ImageTransformation.Builder(new AutofillId(1), null, 1));
}
@Test
+ @SuppressWarnings("deprecation")
public void testNullSubstBuilder() {
assertThrows(IllegalArgumentException.class,
() -> new ImageTransformation.Builder(new AutofillId(1), Pattern.compile(""), 0));
}
@Test
+ @SuppressWarnings("deprecation")
public void fieldCannotBeFound() throws Exception {
AutofillId unknownId = new AutofillId(42);
@@ -82,6 +87,7 @@
}
@Test
+ @SuppressWarnings("deprecation")
public void theOneOptionsMatches() throws Exception {
AutofillId id = new AutofillId(1);
ImageTransformation trans = new ImageTransformation
@@ -99,6 +105,25 @@
}
@Test
+ public void theOneOptionsMatchesWithContentDescription() throws Exception {
+ AutofillId id = new AutofillId(1);
+ ImageTransformation trans = new ImageTransformation
+ .Builder(id, Pattern.compile(".*"), 42, "Are you content?")
+ .build();
+
+ ValueFinder finder = mock(ValueFinder.class);
+ RemoteViews template = mock(RemoteViews.class);
+
+ when(finder.findByAutofillId(id)).thenReturn("val");
+
+ trans.apply(finder, template, 0);
+
+ verify(template).setImageViewResource(0, 42);
+ verify(template).setContentDescription(0, "Are you content?");
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
public void noOptionsMatches() throws Exception {
AutofillId id = new AutofillId(1);
ImageTransformation trans = new ImageTransformation
@@ -116,6 +141,7 @@
}
@Test
+ @SuppressWarnings("deprecation")
public void multipleOptionsOneMatches() throws Exception {
AutofillId id = new AutofillId(1);
ImageTransformation trans = new ImageTransformation
@@ -134,6 +160,26 @@
}
@Test
+ public void multipleOptionsOneMatchesWithContentDescription() throws Exception {
+ AutofillId id = new AutofillId(1);
+ ImageTransformation trans = new ImageTransformation
+ .Builder(id, Pattern.compile(".*1"), 1, "Are you content?")
+ .addOption(Pattern.compile(".*2"), 2, "I am content")
+ .build();
+
+ ValueFinder finder = mock(ValueFinder.class);
+ RemoteViews template = mock(RemoteViews.class);
+
+ when(finder.findByAutofillId(id)).thenReturn("val-2");
+
+ trans.apply(finder, template, 0);
+
+ verify(template).setImageViewResource(0, 2);
+ verify(template).setContentDescription(0, "I am content");
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
public void twoOptionsMatch() throws Exception {
AutofillId id = new AutofillId(1);
ImageTransformation trans = new ImageTransformation
@@ -151,4 +197,24 @@
// If two options match, the first one is picked
verify(template, only()).setImageViewResource(0, 1);
}
+
+ @Test
+ public void twoOptionsMatchWithContentDescription() throws Exception {
+ AutofillId id = new AutofillId(1);
+ ImageTransformation trans = new ImageTransformation
+ .Builder(id, Pattern.compile(".*a.*"), 1, "Are you content?")
+ .addOption(Pattern.compile(".*b.*"), 2, "No, I'm not")
+ .build();
+
+ ValueFinder finder = mock(ValueFinder.class);
+ RemoteViews template = mock(RemoteViews.class);
+
+ when(finder.findByAutofillId(id)).thenReturn("ab");
+
+ trans.apply(finder, template, 0);
+
+ // If two options match, the first one is picked
+ verify(template).setImageViewResource(0, 1);
+ verify(template).setContentDescription(0, "Are you content?");
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 6694e5a..0f2dc82 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -18,24 +18,31 @@
import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
import static android.autofillservice.cts.CannedFillResponse.ResponseType.TIMEOUT;
-import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.IDLE_UNBIND_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.SAVE_TIMEOUT_MS;
import static android.autofillservice.cts.Helper.dumpAutofillService;
import static android.autofillservice.cts.Helper.dumpStructure;
+import static android.autofillservice.cts.Helper.getActivityName;
+import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.IDLE_UNBIND_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.assist.AssistStructure;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.content.ComponentName;
+import android.content.IntentSender;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.SystemClock;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FillCallback;
import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.support.annotation.Nullable;
@@ -53,8 +60,10 @@
*/
public class InstrumentedAutoFillService extends AutofillService {
- static final String SERVICE_NAME = InstrumentedAutoFillService.class.getPackage()
- .getName() + "/." + InstrumentedAutoFillService.class.getSimpleName();
+ static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+ static final String SERVICE_CLASS = "InstrumentedAutoFillService";
+
+ static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
private static final String TAG = "InstrumentedAutoFillService";
@@ -78,10 +87,69 @@
sInstance.set(this);
}
- public static AutofillService peekInstance() {
+ public static InstrumentedAutoFillService peekInstance() {
return sInstance.get();
}
+ /**
+ * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
+ * expected size.
+ */
+ public static List<Event> getFillEvents(int expectedSize) throws Exception {
+ final List<Event> events = getFillEventHistory(expectedSize).getEvents();
+ // Sanity check
+ if (expectedSize > 0 && events == null || events.size() != expectedSize) {
+ throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
+ + ", but it is: " + events);
+ }
+ return events;
+ }
+
+ /**
+ * Gets the {@link FillEventHistory}, waiting until it has the expected size.
+ */
+ public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+ final InstrumentedAutoFillService service = peekInstance();
+
+ if (expectedSize == 0) {
+ // Need to always sleep as there is no condition / callback to be used to wait until
+ // expected number of events is set.
+ SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+ final FillEventHistory history = service.getFillEventHistory();
+ assertThat(history.getEvents()).isNull();
+ return history;
+ }
+
+ return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+ final FillEventHistory history = service.getFillEventHistory();
+ if (history == null) {
+ return null;
+ }
+ final List<Event> events = history.getEvents();
+ if (events != null) {
+ if (events.size() != expectedSize) {
+ Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
+ return null;
+ }
+ } else {
+ Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
+ return null;
+ }
+ return history;
+ });
+ }
+
+ /**
+ * Asserts there is no {@link FillEventHistory}.
+ */
+ public static void assertNoFillEventHistory() {
+ // Need to always sleep as there is no condition / callback to be used to wait until
+ // expected number of events is set.
+ SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+ assertThat(peekInstance().getFillEventHistory()).isNull();
+
+ }
+
@Override
public void onConnected() {
Log.v(TAG, "onConnected(): " + sConnectionStates);
@@ -118,7 +186,8 @@
return;
}
}
- sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback);
+ sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback,
+ request.getDatasetIds());
}
private boolean fromSamePackage(List<FillContext> contexts) {
@@ -152,12 +221,15 @@
* block until the service receives a callback, it should use
* {@link Replier#getNextFillRequest()} instead.
*/
- static void waitUntilConnected() throws InterruptedException {
- final String state = sConnectionStates.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- if (state == null) {
- dumpAutofillService();
- throw new RetryableException("not connected in %d ms", CONNECTION_TIMEOUT_MS);
- }
+ static void waitUntilConnected() throws Exception {
+ final String state = CONNECTION_TIMEOUT.run("waitUntilConnected()", () -> {
+ final String polled =
+ sConnectionStates.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ if (polled == null) {
+ dumpAutofillService();
+ }
+ return polled;
+ });
assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_CONNECTED);
}
@@ -167,12 +239,10 @@
* <p>This method is useful on tests that explicitly verifies the connection, but should be
* avoided in other tests, as it adds extra time to the test execution.
*/
- static void waitUntilDisconnected() throws InterruptedException {
- final String state = sConnectionStates.poll(2 * IDLE_UNBIND_TIMEOUT_MS,
- TimeUnit.MILLISECONDS);
- if (state == null) {
- throw new RetryableException("not disconnected in %d ms", IDLE_UNBIND_TIMEOUT_MS);
- }
+ static void waitUntilDisconnected() throws Exception {
+ final String state = IDLE_UNBIND_TIMEOUT.run("waitUntilDisconnected()", () -> {
+ return sConnectionStates.poll(2 * IDLE_UNBIND_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ });
assertWithMessage("Invalid connection state").that(state).isEqualTo(STATE_DISCONNECTED);
}
@@ -217,12 +287,14 @@
* that can be asserted at the end of a test case.
*/
static final class SaveRequest {
- final List<FillContext> contexts;
- final AssistStructure structure;
- final Bundle data;
- final SaveCallback callback;
+ public final List<FillContext> contexts;
+ public final AssistStructure structure;
+ public final Bundle data;
+ public final SaveCallback callback;
+ public final List<String> datasetIds;
- private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback) {
+ private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+ List<String> datasetIds) {
if (contexts != null && contexts.size() > 0) {
structure = contexts.get(contexts.size() - 1).getStructure();
} else {
@@ -231,6 +303,7 @@
this.contexts = contexts;
this.data = data;
this.callback = callback;
+ this.datasetIds = datasetIds;
}
}
@@ -246,13 +319,13 @@
private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
- private List<Exception> mExceptions;
+ private List<Throwable> mExceptions;
+ private IntentSender mOnSaveIntentSender;
private String mAcceptedPackageName;
private Replier() {
}
-
private IdMode mIdMode = IdMode.RESOURCE_ID;
public void setIdMode(IdMode mode) {
@@ -266,11 +339,11 @@
/**
* Gets the exceptions thrown asynchronously, if any.
*/
- @Nullable List<Exception> getExceptions() {
+ @Nullable List<Throwable> getExceptions() {
return mExceptions;
}
- private void addException(@Nullable Exception e) {
+ private void addException(@Nullable Throwable e) {
if (e == null) return;
if (mExceptions == null) {
@@ -301,15 +374,23 @@
}
/**
+ * Sets the {@link IntentSender} that is passed to
+ * {@link SaveCallback#onSuccess(IntentSender)}.
+ */
+ void setOnSave(IntentSender intentSender) {
+ mOnSaveIntentSender = intentSender;
+ }
+
+ /**
* Gets the next fill request, in the order received.
*
* <p>Typically called at the end of a test case, to assert the initial request.
*/
FillRequest getNextFillRequest() throws InterruptedException {
- final FillRequest request = mFillRequests.poll(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final FillRequest request =
+ mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
if (request == null) {
- throw new RetryableException("onFillRequest() not called in %s ms",
- FILL_TIMEOUT_MS);
+ throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
}
return request;
}
@@ -337,10 +418,10 @@
* <p>Typically called at the end of a test case, to assert the initial request.
*/
SaveRequest getNextSaveRequest() throws InterruptedException {
- final SaveRequest request = mSaveRequests.poll(SAVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final SaveRequest request =
+ mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
if (request == null) {
- throw new RetryableException(
- "onSaveRequest() not called in %d ms", SAVE_TIMEOUT_MS);
+ throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
}
return request;
}
@@ -363,6 +444,7 @@
mFillRequests.clear();
mSaveRequests.clear();
mExceptions = null;
+ mOnSaveIntentSender = null;
mAcceptedPackageName = null;
}
@@ -371,7 +453,7 @@
try {
CannedFillResponse response = null;
try {
- response = mResponses.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted getting CannedResponse: " + e);
Thread.currentThread().interrupt();
@@ -379,7 +461,8 @@
return;
}
if (response == null) {
- final String msg = "onFillRequest() received when no CannedResponse was set";
+ final String msg = "onFillRequest() for activity " + getActivityName(contexts)
+ + " received when no canned response was set.";
dumpStructure(msg, contexts);
addException(new RetryableException(msg));
return;
@@ -413,6 +496,10 @@
fillResponse = response.asFillResponse(
(name) -> Helper.findNodeByHtmlName(contexts, name));
break;
+ case HTML_NAME_OR_RESOURCE_ID:
+ fillResponse = response.asFillResponse(
+ (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
+ break;
default:
throw new IllegalStateException("Unknown id mode: " + mIdMode);
}
@@ -427,10 +514,15 @@
}
}
- private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback) {
- Log.d(TAG, "onSaveRequest()");
- mSaveRequests.offer(new SaveRequest(contexts, data, callback));
- callback.onSuccess();
+ private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+ List<String> datasetIds) {
+ Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
+ mSaveRequests.offer(new SaveRequest(contexts, data, callback, datasetIds));
+ if (mOnSaveIntentSender != null) {
+ callback.onSuccess(mOnSaveIntentSender);
+ } else {
+ callback.onSuccess();
+ }
}
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java b/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
new file mode 100644
index 0000000..3d70bd0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/JUnitHelper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import android.support.annotation.NonNull;
+
+/**
+ * Generic helper for JUnit needs.
+ */
+public final class JUnitHelper {
+
+ private static String sCurrentTestNamer;
+
+ @NonNull
+ static String getCurrentTestName() {
+ return sCurrentTestNamer != null ? sCurrentTestNamer : "N/A";
+ }
+
+ public static void setCurrentTestName(String name) {
+ sCurrentTestNamer = name;
+ }
+
+ private JUnitHelper() {
+ throw new UnsupportedOperationException("contain static methods only");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index b41cef8..2a3f019 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -22,6 +22,9 @@
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
@@ -99,6 +102,42 @@
getAutofillManager().cancel();
});
mCancelButton.setOnClickListener((OnClickListener) v -> finish());
+
+ // Create a custom insertion callback so it just show the AUTOFILL item, otherwise CTS
+ // testAutofillManuallyOneDataset() will fail if a previous test set the clipboard
+ // TODO(b/71711122): remove once there's a proper way to reset the clipboard
+ mUsernameEditText.setCustomInsertionActionModeCallback(new ActionMode.Callback() {
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ final String autofillTitle = AutoFillServiceTestCase.sDefaultUiBot
+ .getAutofillContextualMenuTitle();
+ for (int i = 0; i < menu.size(); i++) {
+ final MenuItem item = menu.getItem(i);
+ final String title = item.getTitle().toString();
+ if (!title.equals(autofillTitle)) {
+ Log.v(TAG, "onPrepareActionMode(): ignoring " + title);
+ menu.removeItem(item.getItemId());
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
+ });
}
protected int getContentView() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 898ca84..8ab6d3b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -20,29 +20,30 @@
import static android.app.Activity.RESULT_OK;
import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
import static android.autofillservice.cts.Helper.ID_PASSWORD;
import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.Helper.assertHasFlags;
import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
import static android.autofillservice.cts.Helper.assertNumberOfChildren;
import static android.autofillservice.cts.Helper.assertTextAndValue;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.assertTextOnly;
import static android.autofillservice.cts.Helper.assertValue;
import static android.autofillservice.cts.Helper.dumpStructure;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.runShellCommand;
import static android.autofillservice.cts.Helper.setUserComplete;
+import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_CLASS;
+import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_PACKAGE;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
@@ -64,13 +65,13 @@
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.graphics.Color;
import android.os.Bundle;
-import android.service.autofill.FillEventHistory;
import android.service.autofill.SaveInfo;
import android.support.test.uiautomator.UiObject2;
import android.util.Log;
@@ -84,7 +85,6 @@
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -92,6 +92,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
/**
* This is the test case covering most scenarios - other test cases will cover characteristics
@@ -112,11 +113,6 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
@Test
public void testAutoFillNoDatasets() throws Exception {
// Set service.
@@ -133,14 +129,7 @@
sReplier.getNextFillRequest();
// Make sure UI is not shown.
- sUiBot.assertNoDatasets();
-
- // Try to trigger it again...
-
- mActivity.onPassword(View::requestFocus);
- // ...and make sure it didn't
- sUiBot.assertNoDatasets();
- sReplier.assertNumberUnhandledFillRequests(0);
+ mUiBot.assertNoDatasets();
// Test connection lifecycle.
waitUntilDisconnected();
@@ -148,18 +137,28 @@
@Test
public void testAutofillManuallyAfterServiceReturnedNoDatasets() throws Exception {
+ autofillAfterServiceReturnedNoDatasets(true);
+ }
+
+ @Test
+ public void testAutofillAutomaticallyAfterServiceReturnedNoDatasets() throws Exception {
+ autofillAfterServiceReturnedNoDatasets(false);
+ }
+
+ private void autofillAfterServiceReturnedNoDatasets(boolean manually) throws Exception {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(NO_RESPONSE);
+ mActivity.expectAutoFill("dude", "sweet");
// Trigger autofill.
mActivity.onUsername(View::requestFocus);
sReplier.getNextFillRequest();
// Make sure UI is not shown.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Try again, forcing it
sReplier.addResponse(new CannedDataset.Builder()
@@ -167,15 +166,21 @@
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build());
- mActivity.expectAutoFill("dude", "sweet");
- mActivity.forceAutofillOnUsername();
+ final int expectedFlags;
+ if (manually) {
+ expectedFlags = FLAG_MANUAL_REQUEST;
+ mActivity.forceAutofillOnUsername();
+ } else {
+ expectedFlags = 0;
+ mActivity.onPassword(View::requestFocus);
+ }
final FillRequest fillRequest = sReplier.getNextFillRequest();
- assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest.flags, expectedFlags);
- // Selects the dataset.
- sUiBot.selectDataset("The Dude");
+ // Select the dataset.
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -183,6 +188,15 @@
@Test
public void testAutofillManuallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+ autofillAndSaveAfterServiceReturnedNoDatasets(true);
+ }
+
+ @Test
+ public void testAutofillAutomaticallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+ autofillAndSaveAfterServiceReturnedNoDatasets(false);
+ }
+
+ private void autofillAndSaveAfterServiceReturnedNoDatasets(boolean manually) throws Exception {
// Set service.
enableService();
@@ -190,31 +204,98 @@
sReplier.addResponse(NO_RESPONSE);
// Trigger autofill.
- mActivity.onUsername(View::requestFocus);
+ // NOTE: must be on password, as saveOnlyTest() will trigger on username
+ mActivity.onPassword(View::requestFocus);
sReplier.getNextFillRequest();
// Make sure UI is not shown.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
sReplier.assertNumberUnhandledFillRequests(0);
mActivity.onPassword(View::requestFocus);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
sReplier.assertNumberUnhandledFillRequests(0);
// Try again, forcing it
- saveOnlyTest(true);
+ saveOnlyTest(manually);
}
@Test
- public void testAutoFillOneDataset() throws Exception {
+ public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
// Set service.
enableService();
- // Set expectations.
+ // First request
sReplier.addResponse(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build());
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("The Dude");
+
+ // Second request
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "DUDE")
+ .setField(ID_PASSWORD, "SWEET")
+ .setPresentation(createPresentation("THE DUDE"))
+ .build());
+
+ mActivity.forceAutofillOnUsername();
+ final FillRequest secondRequest = sReplier.getNextFillRequest();
+ assertHasFlags(secondRequest.flags, FLAG_MANUAL_REQUEST);
+ mUiBot.assertDatasets("THE DUDE");
+ }
+
+ @Test
+ public void testAutoFillOneDataset() throws Exception {
+ autofillOneDatasetTest(BorderType.NONE);
+ }
+
+ @Test
+ public void testAutoFillOneDataset_withHeader() throws Exception {
+ autofillOneDatasetTest(BorderType.HEADER_ONLY);
+ }
+
+ @Test
+ public void testAutoFillOneDataset_withFooter() throws Exception {
+ autofillOneDatasetTest(BorderType.FOOTER_ONLY);
+ }
+
+ @Test
+ public void testAutoFillOneDataset_withHeaderAndFooter() throws Exception {
+ autofillOneDatasetTest(BorderType.BOTH);
+ }
+
+ private enum BorderType {
+ NONE,
+ HEADER_ONLY,
+ FOOTER_ONLY,
+ BOTH
+ }
+
+ private void autofillOneDatasetTest(BorderType borderType) throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ String expectedHeader = null, expectedFooter = null;
+
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Dude"))
+ .build());
+ if (borderType == BorderType.BOTH || borderType == BorderType.HEADER_ONLY) {
+ expectedHeader = "Head";
+ builder.setHeader(createPresentation(expectedHeader));
+ }
+ if (borderType == BorderType.BOTH || borderType == BorderType.FOOTER_ONLY) {
+ expectedFooter = "Tails";
+ builder.setFooter(createPresentation(expectedFooter));
+ }
+ sReplier.addResponse(builder.build());
mActivity.expectAutoFill("dude", "sweet");
// Dynamically set password to make sure it's sanitized.
@@ -224,7 +305,10 @@
mActivity.onUsername(View::requestFocus);
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ final UiObject2 picker = mUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
+ "The Dude");
+
+ mUiBot.selectDataset(picker, "The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -268,14 +352,14 @@
sReplier.getNextFillRequest();
// Make sure all datasets are available...
- sUiBot.assertDatasets("The Dude", "THE DUDE");
+ mUiBot.assertDatasets("The Dude", "THE DUDE");
// ... on all fields.
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("The Dude", "THE DUDE");
+ mUiBot.assertDatasets("The Dude", "THE DUDE");
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -318,19 +402,19 @@
sReplier.getNextFillRequest();
// Make sure all datasets are available on username...
- sUiBot.assertDatasets("The Dude", "THE DUDE");
+ mUiBot.assertDatasets("The Dude", "THE DUDE");
// ... but just one for password
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("The Dude");
+ mUiBot.assertDatasets("The Dude");
// Auto-fill it.
mActivity.onUsername(View::requestFocus);
- sUiBot.assertDatasets("The Dude", "THE DUDE");
+ mUiBot.assertDatasets("The Dude", "THE DUDE");
if (fillsAll) {
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
} else {
- sUiBot.selectDataset("THE DUDE");
+ mUiBot.selectDataset("THE DUDE");
}
// Check the results.
@@ -338,6 +422,43 @@
}
@Test
+ public void testAutoFillDatasetWithoutFieldIsIgnored() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Dude"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "DUDE")
+ .setField(ID_PASSWORD, "SWEET")
+ .build())
+ .build());
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Make sure all datasets are available...
+ mUiBot.assertDatasets("The Dude");
+
+ // ... on all fields.
+ mActivity.onPassword(View::requestFocus);
+ mUiBot.assertDatasets("The Dude");
+
+ // Auto-fill it.
+ mUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
@@ -383,7 +504,7 @@
sReplier.assertNumberUnhandledFillRequests(0);
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -409,17 +530,17 @@
// Trigger auto-fill.
mActivity.onUsername(View::requestFocus);
sReplier.getNextFillRequest();
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
// Make sure tapping on autofilled field does not trigger it again
mActivity.onPassword(View::requestFocus);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
mActivity.onUsername(View::requestFocus);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
}
@Test
@@ -453,7 +574,7 @@
callback.assertNumberUnhandledEvents(0);
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -499,7 +620,7 @@
sReplier.getNextFillRequest();
// Auto-fill it.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Assert callback was called
final View username = mActivity.getUsername();
@@ -517,6 +638,7 @@
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
+ .setId("I'm the alpha and the omega")
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
@@ -532,10 +654,18 @@
// Since this is a Presubmit test, wait for connection to avoid flakiness.
waitUntilConnected();
- sReplier.getNextFillRequest();
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+ // Make sure input was sanitized...
+ assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+ assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+ // ...but labels weren't
+ assertTextOnly(fillRequest.structure, ID_USERNAME_LABEL, "Username");
+ assertTextOnly(fillRequest.structure, ID_PASSWORD_LABEL, "Password");
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -554,15 +684,17 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
// Assert value of expected fields - should not be sanitized.
- final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
- assertTextAndValue(username, "dude");
- final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
- assertTextAndValue(password, "dude");
+ assertTextAndValue(saveRequest.structure, ID_USERNAME, "dude");
+ assertTextAndValue(saveRequest.structure, ID_PASSWORD, "dude");
+ assertTextOnly(saveRequest.structure, ID_USERNAME_LABEL, "Username");
+ assertTextOnly(saveRequest.structure, ID_PASSWORD_LABEL, "Password");
// Make sure extras were passed back on onSave()
assertThat(saveRequest.data).isNotNull();
@@ -608,7 +740,7 @@
runShellCommand("appops set %s SYSTEM_ALERT_WINDOW allow", mPackageName);
// Make sure the fill UI is shown.
- sUiBot.assertDatasets("The Dude");
+ mUiBot.assertDatasets("The Dude");
final CountDownLatch latch = new CountDownLatch(1);
@@ -638,7 +770,7 @@
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -658,14 +790,14 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
// Assert value of expected fields - should not be sanitized.
final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
assertTextAndValue(username, "dude");
- final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+ final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
assertTextAndValue(password, "dude");
// Make sure extras were passed back on onSave()
@@ -747,10 +879,10 @@
sReplier.getNextFillRequest();
// Make sure all datasets are shown.
- final UiObject2 picker = sUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
+ final UiObject2 picker = mUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
// Auto-fill it.
- sUiBot.selectDataset(picker, name);
+ mUiBot.selectDataset(picker, name);
// Check the results.
mActivity.assertAutoFilled();
@@ -779,17 +911,17 @@
sReplier.getNextFillRequest();
// Check initial field.
- sUiBot.assertDatasets("The Dude");
+ mUiBot.assertDatasets("The Dude");
// Then move around...
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("Dude's password");
+ mUiBot.assertDatasets("Dude's password");
mActivity.onUsername(View::requestFocus);
- sUiBot.assertDatasets("The Dude");
+ mUiBot.assertDatasets("The Dude");
// Auto-fill it.
mActivity.onPassword(View::requestFocus);
- sUiBot.selectDataset("Dude's password");
+ mUiBot.selectDataset("Dude's password");
// Check the results.
mActivity.assertAutoFilled();
@@ -823,17 +955,17 @@
sReplier.getNextFillRequest();
// Check initial field.
- sUiBot.assertDatasets("Dataset1", "User2");
+ mUiBot.assertDatasets("Dataset1", "User2");
// Then move around...
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("Pass1", "Dataset2");
+ mUiBot.assertDatasets("Pass1", "Dataset2");
mActivity.onUsername(View::requestFocus);
- sUiBot.assertDatasets("Dataset1", "User2");
+ mUiBot.assertDatasets("Dataset1", "User2");
// Auto-fill it.
mActivity.onPassword(View::requestFocus);
- sUiBot.selectDataset("Pass1");
+ mUiBot.selectDataset("Pass1");
// Check the results.
mActivity.assertAutoFilled();
@@ -866,17 +998,17 @@
sReplier.getNextFillRequest();
// Check initial field.
- sUiBot.assertDatasets("User1", "User2");
+ mUiBot.assertDatasets("User1", "User2");
// Then move around...
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("Pass1", "Pass2");
+ mUiBot.assertDatasets("Pass1", "Pass2");
mActivity.onUsername(View::requestFocus);
- sUiBot.assertDatasets("User1", "User2");
+ mUiBot.assertDatasets("User1", "User2");
// Auto-fill it.
mActivity.onPassword(View::requestFocus);
- sUiBot.selectDataset("Pass1");
+ mUiBot.selectDataset("Pass1");
// Check the results.
mActivity.assertAutoFilled();
@@ -909,16 +1041,16 @@
sReplier.getNextFillRequest();
// Check initial field.
- sUiBot.assertDatasets("User1", "User2");
+ mUiBot.assertDatasets("User1", "User2");
// Then move around...
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("Pass2");
+ mUiBot.assertDatasets("Pass2");
mActivity.onUsername(View::requestFocus);
- sUiBot.assertDatasets("User1", "User2");
+ mUiBot.assertDatasets("User1", "User2");
// Auto-fill it.
- sUiBot.selectDataset("User2");
+ mUiBot.selectDataset("User2");
// Check the results.
mActivity.assertAutoFilled();
@@ -951,16 +1083,16 @@
sReplier.getNextFillRequest();
// Check initial field.
- sUiBot.assertDatasets("User1");
+ mUiBot.assertDatasets("User1");
// Then move around...
mActivity.onPassword(View::requestFocus);
- sUiBot.assertDatasets("Pass1", "Pass2");
+ mUiBot.assertDatasets("Pass1", "Pass2");
mActivity.onUsername(View::requestFocus);
- sUiBot.assertDatasets("User1");
+ mUiBot.assertDatasets("User1");
// Auto-fill it.
- sUiBot.selectDataset("User1");
+ mUiBot.selectDataset("User1");
// Check the results.
mActivity.assertAutoFilled();
@@ -995,29 +1127,29 @@
sReplier.getNextFillRequest();
// With no filter text all datasets should be shown
- sUiBot.assertDatasets(AA, AB, B);
+ mUiBot.assertDatasets(AA, AB, B);
// Only two datasets start with 'a'
runShellCommand("input keyevent KEYCODE_A");
- sUiBot.assertDatasets(AA, AB);
+ mUiBot.assertDatasets(AA, AB);
// Only one dataset start with 'aa'
runShellCommand("input keyevent KEYCODE_A");
- sUiBot.assertDatasets(AA);
+ mUiBot.assertDatasets(AA);
// Only two datasets start with 'a'
runShellCommand("input keyevent KEYCODE_DEL");
- sUiBot.assertDatasets(AA, AB);
+ mUiBot.assertDatasets(AA, AB);
// With no filter text all datasets should be shown
runShellCommand("input keyevent KEYCODE_DEL");
- sUiBot.assertDatasets(AA, AB, B);
+ mUiBot.assertDatasets(AA, AB, B);
// No dataset start with 'aaa'
runShellCommand("input keyevent KEYCODE_A");
runShellCommand("input keyevent KEYCODE_A");
runShellCommand("input keyevent KEYCODE_A");
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
}
@Test
@@ -1049,29 +1181,29 @@
sReplier.getNextFillRequest();
// With no filter text all datasets should be shown
- sUiBot.assertDatasets(AA, AB, B);
+ mUiBot.assertDatasets(AA, AB, B);
// Two datasets start with 'a' and one with null value always shown
runShellCommand("input keyevent KEYCODE_A");
- sUiBot.assertDatasets(AA, AB, B);
+ mUiBot.assertDatasets(AA, AB, B);
// One dataset start with 'aa' and one with null value always shown
runShellCommand("input keyevent KEYCODE_A");
- sUiBot.assertDatasets(AA, B);
+ mUiBot.assertDatasets(AA, B);
// Two datasets start with 'a' and one with null value always shown
runShellCommand("input keyevent KEYCODE_DEL");
- sUiBot.assertDatasets(AA, AB, B);
+ mUiBot.assertDatasets(AA, AB, B);
// With no filter text all datasets should be shown
runShellCommand("input keyevent KEYCODE_DEL");
- sUiBot.assertDatasets(AA, AB, B);
+ mUiBot.assertDatasets(AA, AB, B);
// No dataset start with 'aaa' and one with null value always shown
runShellCommand("input keyevent KEYCODE_A");
runShellCommand("input keyevent KEYCODE_A");
runShellCommand("input keyevent KEYCODE_A");
- sUiBot.assertDatasets(B);
+ mUiBot.assertDatasets(B);
}
@Test
@@ -1103,16 +1235,71 @@
sReplier.getNextFillRequest();
// With no filter text all datasets should be shown
- sUiBot.assertDatasets(A, B, C);
+ mUiBot.assertDatasets(A, B, C);
mActivity.onUsername((v) -> v.setText("a"));
- sUiBot.assertDatasets(A);
+ mUiBot.assertDatasets(A);
mActivity.onUsername((v) -> v.setText("b"));
- sUiBot.assertDatasets(B);
+ mUiBot.assertDatasets(B);
mActivity.onUsername((v) -> v.setText("c"));
- sUiBot.assertDatasets(C);
+ mUiBot.assertDatasets(C);
+ }
+
+ @Test
+ public void filterTextUsingRegex() throws Exception {
+ // Dataset presentations.
+ final String aa = "Two A's";
+ final String ab = "A and B";
+ final String b = "Only B";
+
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
+ .setPresentation(createPresentation(aa))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "whatsoever", createPresentation(ab),
+ Pattern.compile("a|ab"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
+ .setPresentation(createPresentation(b))
+ .build())
+ .build());
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // With no filter text all datasets should be shown
+ mUiBot.assertDatasets(aa, ab, b);
+
+ // Only two datasets start with 'a'
+ runShellCommand("input keyevent KEYCODE_A");
+ mUiBot.assertDatasets(aa, ab);
+
+ // Only one dataset start with 'aa'
+ runShellCommand("input keyevent KEYCODE_A");
+ mUiBot.assertDatasets(aa);
+
+ // Only two datasets start with 'a'
+ runShellCommand("input keyevent KEYCODE_DEL");
+ mUiBot.assertDatasets(aa, ab);
+
+ // With no filter text all datasets should be shown
+ runShellCommand("input keyevent KEYCODE_DEL");
+ mUiBot.assertDatasets(aa, ab, b);
+
+ // No dataset start with 'aaa'
+ runShellCommand("input keyevent KEYCODE_A");
+ runShellCommand("input keyevent KEYCODE_A");
+ runShellCommand("input keyevent KEYCODE_A");
+ mUiBot.assertNoDatasets();
}
@Test
@@ -1141,7 +1328,7 @@
}
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started
@@ -1157,10 +1344,11 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
sReplier.assertNumberUnhandledSaveRequests(0);
+ assertThat(saveRequest.datasetIds).isNull();
// Assert value of expected fields - should not be sanitized.
try {
@@ -1193,12 +1381,12 @@
startCheckoutActivityAsNewTask();
try {
// .. then the real activity being tested.
- sUiBot.switchAppsUsingRecents();
- sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+ mUiBot.switchAppsUsingRecents();
+ mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
saveGoesAway(DismissType.RECENTS_BUTTON);
} finally {
- CheckoutActivity.finishIt();
+ CheckoutActivity.finishIt(mUiBot);
}
}
@@ -1207,12 +1395,12 @@
saveGoesAway(DismissType.TOUCH_OUTSIDE);
}
- private void startCheckoutActivityAsNewTask() {
+ private void startCheckoutActivityAsNewTask() throws Exception {
final Intent intent = new Intent(mContext, CheckoutActivity.class);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
mContext.startActivity(intent);
- sUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
+ mUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
}
private void saveGoesAway(DismissType dismissType) throws Exception {
@@ -1227,7 +1415,7 @@
mActivity.onUsername(View::requestFocus);
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started
@@ -1243,27 +1431,27 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
// Then make sure it goes away when user doesn't want it..
switch (dismissType) {
case BACK_BUTTON:
- sUiBot.pressBack();
+ mUiBot.pressBack();
break;
case HOME_BUTTON:
- sUiBot.pressHome();
+ mUiBot.pressHome();
break;
case TOUCH_OUTSIDE:
- sUiBot.assertShownByText(expectedMessage).click();
+ mUiBot.assertShownByText(expectedMessage).click();
break;
case RECENTS_BUTTON:
- sUiBot.switchAppsUsingRecents();
- sUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
+ mUiBot.switchAppsUsingRecents();
+ mUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
break;
default:
throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
}
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
@@ -1296,7 +1484,7 @@
}
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started
@@ -1312,7 +1500,7 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
sReplier.assertNumberUnhandledSaveRequests(0);
@@ -1350,7 +1538,7 @@
// Wait for onFill() before changing value, otherwise the fields might be changed before
// the session started
sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Set credentials...
mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
@@ -1361,7 +1549,7 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
sReplier.assertNumberUnhandledSaveRequests(0);
@@ -1395,7 +1583,7 @@
mActivity.onUsername(View::requestFocus);
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started
@@ -1412,7 +1600,7 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -1460,7 +1648,7 @@
mActivity.onUsername(View::requestFocus);
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started
@@ -1485,13 +1673,13 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
if (filledFields == FilledFields.NONE) {
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
assertNoDanglingSessions();
return;
}
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -1553,7 +1741,7 @@
mActivity.onUsername(View::requestFocus);
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started.
@@ -1569,14 +1757,50 @@
assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
// Assert the snack bar is shown and tap "Save".
- final UiObject2 saveSnackBar = sUiBot.assertSaveShowing(saveDescription, type);
- sUiBot.saveForAutofill(saveSnackBar, true);
+ final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, type);
+ mUiBot.saveForAutofill(saveSnackBar, true);
// Assert save was called.
sReplier.getNextSaveRequest();
}
@Test
+ public void testDontTriggerSaveOnFinishWhenRequestedByFlag() throws Exception {
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+ .build());
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+
+ // Sanity check.
+ mUiBot.assertNoDatasets();
+
+ // Wait for onFill() before proceeding, otherwise the fields might be changed before
+ // the session started
+ sReplier.getNextFillRequest();
+
+ // Set credentials...
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+
+ // ...and login
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+ // Make sure it didn't trigger save.
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Sanity check: session should have been canceled
+ assertNoDanglingSessions();
+ }
+
+ @Test
public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
mActivity.setFlags(FLAG_SECURE);
testAutoFillOneDatasetAndSave();
@@ -1633,14 +1857,14 @@
sReplier.getNextFillRequest();
final View username = mActivity.getUsername();
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// Make sure UI is show on 2nd field as well
final View password = mActivity.getPassword();
mActivity.onPassword(View::requestFocus);
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(password);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// Now tap on 1st field to show it again...
mActivity.onUsername(View::requestFocus);
@@ -1650,16 +1874,16 @@
if (cancelFirstAttempt) {
// Trigger the auth dialog, but emulate cancel.
AuthenticationActivity.setResultCode(RESULT_CANCELED);
- sUiBot.selectDataset("Tap to auth response");
+ mUiBot.selectDataset("Tap to auth response");
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// Make sure it's still shown on other fields...
mActivity.onPassword(View::requestFocus);
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(password);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// Tap on 1st field to show it again...
mActivity.onUsername(View::requestFocus);
@@ -1669,13 +1893,13 @@
// ...and select it this time
AuthenticationActivity.setResultCode(RESULT_OK);
- sUiBot.selectDataset("Tap to auth response");
+ mUiBot.selectDataset("Tap to auth response");
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(username);
- final UiObject2 picker = sUiBot.assertDatasets("Dataset");
- sUiBot.selectDataset(picker, "Dataset");
+ final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+ mUiBot.selectDataset(picker, "Dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -1722,25 +1946,25 @@
sReplier.getNextFillRequest();
final View username = mActivity.getUsername();
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// Make sure UI is not show on 2nd field
mActivity.onPassword(View::requestFocus);
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Now tap on 1st field to show it again...
mActivity.onUsername(View::requestFocus);
callback.assertUiShownEvent(username);
// ...and select it this time
- sUiBot.selectDataset("Tap to auth response");
+ mUiBot.selectDataset("Tap to auth response");
callback.assertUiHiddenEvent(username);
- final UiObject2 picker = sUiBot.assertDatasets("Dataset");
+ final UiObject2 picker = mUiBot.assertDatasets("Dataset");
callback.assertUiShownEvent(username);
- sUiBot.selectDataset(picker, "Dataset");
+ mUiBot.selectDataset(picker, "Dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -1780,7 +2004,7 @@
sReplier.getNextFillRequest();
final View username = mActivity.getUsername();
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// Disables autofill so it's not triggered again after the auth activity is finished
// (and current session is canceled) and the login activity is resumed.
@@ -1790,7 +2014,7 @@
final CountDownLatch latch = new CountDownLatch(1);
AuthenticationActivity.setResultCode(latch, RESULT_OK);
- sUiBot.selectDataset("Tap to auth response");
+ mUiBot.selectDataset("Tap to auth response");
callback.assertUiHiddenEvent(username);
// Cancel session...
@@ -1799,7 +2023,7 @@
// ...before finishing the Auth UI.
latch.countDown();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
}
@Test
@@ -1842,9 +2066,112 @@
callback.assertUiShownEvent(username);
// Select the authentication dialog.
- sUiBot.selectDataset("Tap to auth response");
+ mUiBot.selectDataset("Tap to auth response");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
+ }
+
+ @Test
+ public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception {
+ fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+ }
+
+ @Test
+ public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception {
+ fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+ }
+
+ @Test
+ public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+ fillResponseAuthWithClientState(ClientStateLocation.BOTH);
+ }
+
+ enum ClientStateLocation {
+ INTENT_ONLY,
+ FILL_RESPONSE_ONLY,
+ BOTH
+ }
+
+ private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception {
+ // Set service.
+ enableService();
+
+ // Prepare the authenticated response
+ final CannedFillResponse.Builder authenticatedResponseBuilder =
+ new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Dataset"))
+ .build());
+
+ if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) {
+ authenticatedResponseBuilder.setExtras(newClientState("CSI", "FromAuthResponse"));
+ }
+
+ final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+ ? AuthenticationActivity.createSender(mContext, 1,
+ authenticatedResponseBuilder.build())
+ : AuthenticationActivity.createSender(mContext, 1,
+ authenticatedResponseBuilder.build(), newClientState("CSI", "FromIntent"));
+
+ // Configure the service behavior
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setAuthentication(authentication, ID_USERNAME)
+ .setIgnoreFields(ID_PASSWORD)
+ .setPresentation(createPresentation("Tap to auth response"))
+ .setExtras(newClientState("CSI", "FromResponse"))
+ .build());
+
+ // Set expectation for the activity
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger autofill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Tap authentication request.
+ mUiBot.selectDataset("Tap to auth response");
+
+ // Tap dataset.
+ mUiBot.selectDataset("Dataset");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+
+ // Now trigger save.
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+ // Assert client state on authentication activity.
+ assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse");
+
+ // Assert client state on save request.
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+ ? "FromAuthResponse" : "FromIntent";
+ assertClientState("on save", saveRequest.data, "CSI", expectedValue);
+ }
+
+ // TODO(on master): move to helper / reuse in other places
+ private void assertClientState(String where, Bundle data, String expectedKey,
+ String expectedValue) {
+ assertWithMessage("no client state on %s", where).that(data).isNotNull();
+ final String extraValue = data.getString(expectedKey);
+ assertWithMessage("invalid value for %s on %s", expectedKey, where)
+ .that(extraValue).isEqualTo(expectedValue);
+ }
+
+ // TODO(on master): move to helper / reuse in other places
+ private Bundle newClientState(String key, String value) {
+ final Bundle clientState = new Bundle();
+ clientState.putString(key, value);
+ return clientState;
}
@Test
@@ -1885,27 +2212,27 @@
// Make sure it's showing initially...
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// ..then type something to hide it.
runShellCommand("input keyevent KEYCODE_A");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Now delete the char and assert it's shown again...
runShellCommand("input keyevent KEYCODE_DEL");
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth response");
+ mUiBot.assertDatasets("Tap to auth response");
// ...and select it this time
AuthenticationActivity.setResultCode(RESULT_OK);
- sUiBot.selectDataset("Tap to auth response");
+ mUiBot.selectDataset("Tap to auth response");
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(username);
- final UiObject2 picker = sUiBot.assertDatasets("Dataset");
- sUiBot.selectDataset(picker, "Dataset");
+ final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+ mUiBot.selectDataset(picker, "Dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -1927,10 +2254,6 @@
}
private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
@@ -1940,14 +2263,13 @@
new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(bogusPresentation)
.build());
// Configure the service behavior
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
- .setField(ID_USERNAME, bogusValue)
- .setField(ID_PASSWORD, bogusValue)
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("Tap to auth dataset"))
.setAuthentication(authentication)
.build())
@@ -1963,34 +2285,34 @@
sReplier.getNextFillRequest();
final View username = mActivity.getUsername();
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
// Make sure UI is show on 2nd field as well
final View password = mActivity.getPassword();
mActivity.onPassword(View::requestFocus);
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(password);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
// Now tap on 1st field to show it again...
mActivity.onUsername(View::requestFocus);
callback.assertUiHiddenEvent(password);
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
if (cancelFirstAttempt) {
// Trigger the auth dialog, but emulate cancel.
AuthenticationActivity.setResultCode(RESULT_CANCELED);
- sUiBot.selectDataset("Tap to auth dataset");
+ mUiBot.selectDataset("Tap to auth dataset");
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
// Make sure it's still shown on other fields...
mActivity.onPassword(View::requestFocus);
callback.assertUiHiddenEvent(username);
callback.assertUiShownEvent(password);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
// Tap on 1st field to show it again...
mActivity.onUsername(View::requestFocus);
@@ -2000,9 +2322,9 @@
// ...and select it this time
AuthenticationActivity.setResultCode(RESULT_OK);
- sUiBot.selectDataset("Tap to auth dataset");
+ mUiBot.selectDataset("Tap to auth dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -2051,14 +2373,14 @@
// Authenticate
callback.assertUiShownEvent(username);
- sUiBot.selectDataset("Tap to auth dataset");
+ mUiBot.selectDataset("Tap to auth dataset");
callback.assertUiHiddenEvent(username);
// Select a dataset from the new response
callback.assertUiShownEvent(username);
- sUiBot.selectDataset("Dataset");
+ mUiBot.selectDataset("Dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -2080,7 +2402,6 @@
new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(createPresentation("Dataset"))
.build());
// Configure the service behavior
@@ -2105,9 +2426,9 @@
// Authenticate
callback.assertUiShownEvent(username);
- sUiBot.selectDataset("Tap to auth dataset");
+ mUiBot.selectDataset("Tap to auth dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -2115,10 +2436,6 @@
@Test
public void testDatasetAuthTwoDatasets() throws Exception {
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
@@ -2127,7 +2444,6 @@
final CannedDataset unlockedDataset = new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(bogusPresentation)
.build();
final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
unlockedDataset);
@@ -2137,14 +2453,14 @@
// Configure the service behavior
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
- .setField(ID_USERNAME, bogusValue)
- .setField(ID_PASSWORD, bogusValue)
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("Tap to auth dataset 1"))
.setAuthentication(authentication1)
.build())
.addDataset(new CannedDataset.Builder()
- .setField(ID_USERNAME, bogusValue)
- .setField(ID_PASSWORD, bogusValue)
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("Tap to auth dataset 2"))
.setAuthentication(authentication2)
.build())
@@ -2162,11 +2478,11 @@
// Authenticate
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
+ mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
- sUiBot.selectDataset("Tap to auth dataset 1");
+ mUiBot.selectDataset("Tap to auth dataset 1");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -2192,7 +2508,6 @@
new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(createPresentation("Dataset"))
.build());
// Configure the service behavior
@@ -2226,23 +2541,19 @@
// Authenticate
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+ mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
- sUiBot.selectDataset(chosenOne);
+ mUiBot.selectDataset(chosenOne);
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
}
@Test
- public void testDatasetAuthFiltering() throws Exception {
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
+ public void testDatasetAuthNoFiltering() throws Exception {
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
@@ -2251,7 +2562,6 @@
final CannedDataset unlockedDataset = new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(bogusPresentation)
.build();
final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
unlockedDataset);
@@ -2259,8 +2569,8 @@
// Configure the service behavior
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
- .setField(ID_USERNAME, bogusValue)
- .setField(ID_PASSWORD, bogusValue)
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("Tap to auth dataset"))
.setAuthentication(authentication)
.build())
@@ -2278,22 +2588,177 @@
// Make sure it's showing initially...
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
// ..then type something to hide it.
runShellCommand("input keyevent KEYCODE_A");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Now delete the char and assert it's shown again...
runShellCommand("input keyevent KEYCODE_DEL");
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset");
+ mUiBot.assertDatasets("Tap to auth dataset");
// ...and select it this time
- sUiBot.selectDataset("Tap to auth dataset");
+ mUiBot.selectDataset("Tap to auth dataset");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ public void testDatasetAuthFilteringUsingAutofillValue() throws Exception {
+ // Set service.
+ enableService();
+ final MyAutofillCallback callback = mActivity.registerCallback();
+
+ // Create the authentication intents
+ final CannedDataset unlockedDataset = new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .build();
+ final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+ unlockedDataset);
+
+ // Configure the service behavior
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("DS1"))
+ .setAuthentication(authentication)
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "DUDE,THE")
+ .setField(ID_PASSWORD, "SWEET")
+ .setPresentation(createPresentation("DS2"))
+ .setAuthentication(authentication)
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "ZzBottom")
+ .setField(ID_PASSWORD, "top")
+ .setPresentation(createPresentation("DS3"))
+ .setAuthentication(authentication)
+ .build())
+ .build());
+
+ // Set expectation for the activity
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+
+ // Wait for onFill() before proceeding.
+ sReplier.getNextFillRequest();
+ final View username = mActivity.getUsername();
+
+ // Make sure it's showing initially...
+ callback.assertUiShownEvent(username);
+ mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+ // ...then type something to hide them.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiHiddenEvent(username);
+ mUiBot.assertNoDatasets();
+
+ // Now delete the char and assert they're shown again...
+ runShellCommand("input keyevent KEYCODE_DEL");
+ callback.assertUiShownEvent(username);
+ mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+ // ...then filter for 2
+ runShellCommand("input keyevent KEYCODE_D");
+ mUiBot.assertDatasets("DS1", "DS2");
+
+ // ...up to 1
+ runShellCommand("input keyevent KEYCODE_U");
+ mUiBot.assertDatasets("DS1", "DS2");
+ runShellCommand("input keyevent KEYCODE_D");
+ mUiBot.assertDatasets("DS1", "DS2");
+ runShellCommand("input keyevent KEYCODE_E");
+ mUiBot.assertDatasets("DS1", "DS2");
+ runShellCommand("input keyevent KEYCODE_COMMA");
+ mUiBot.assertDatasets("DS2");
+
+ // Now delete the char and assert 2 are shown again...
+ runShellCommand("input keyevent KEYCODE_DEL");
+ final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2");
+
+ // ...and select it this time
+ mUiBot.selectDataset(picker, "DS1");
+ callback.assertUiHiddenEvent(username);
+ mUiBot.assertNoDatasets();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ public void testDatasetAuthFilteringUsingRegex() throws Exception {
+ // Set service.
+ enableService();
+ final MyAutofillCallback callback = mActivity.registerCallback();
+
+ // Create the authentication intents
+ final CannedDataset unlockedDataset = new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .build();
+ final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+ unlockedDataset);
+
+ // Configure the service behavior
+
+ final Pattern min2Chars = Pattern.compile(".{2,}");
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+ .setPresentation(createPresentation("Tap to auth dataset"))
+ .setAuthentication(authentication)
+ .build())
+ .build());
+
+ // Set expectation for the activity
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+
+ // Wait for onFill() before proceeding.
+ sReplier.getNextFillRequest();
+ final View username = mActivity.getUsername();
+
+ // Make sure it's showing initially...
+ callback.assertUiShownEvent(username);
+ mUiBot.assertDatasets("Tap to auth dataset");
+
+ // ...then type something to hide it.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiHiddenEvent(username);
+ mUiBot.assertNoDatasets();
+
+ // ...now type something again to show it, as the input will have 2 chars.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiShownEvent(username);
+ mUiBot.assertDatasets("Tap to auth dataset");
+
+ // Delete the char and assert it's not shown again...
+ runShellCommand("input keyevent KEYCODE_DEL");
+ callback.assertUiHiddenEvent(username);
+ mUiBot.assertNoDatasets();
+
+ // ...then type something again to show it, as the input will have 2 chars.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiShownEvent(username);
+
+ // ...and select it this time
+ mUiBot.selectDataset("Tap to auth dataset");
+ callback.assertUiHiddenEvent(username);
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
@@ -2310,10 +2775,6 @@
}
private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
@@ -2322,7 +2783,6 @@
final CannedDataset unlockedDataset = new CannedDataset.Builder()
.setField(ID_USERNAME, "DUDE")
.setField(ID_PASSWORD, "SWEET")
- .setPresentation(bogusPresentation)
.build();
final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
unlockedDataset);
@@ -2330,8 +2790,8 @@
// Configure the service behavior
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
- .setField(ID_USERNAME, bogusValue)
- .setField(ID_PASSWORD, bogusValue)
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("Tap to auth dataset"))
.setAuthentication(authentication)
.build())
@@ -2358,37 +2818,110 @@
// Make sure it's showing initially...
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+ mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
// Filter the auth dataset.
runShellCommand("input keyevent KEYCODE_D");
- sUiBot.assertDatasets("What, me auth?");
+ mUiBot.assertDatasets("What, me auth?");
// Filter all.
runShellCommand("input keyevent KEYCODE_W");
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Now delete the char and assert the non-auth is shown again.
runShellCommand("input keyevent KEYCODE_DEL");
callback.assertUiShownEvent(username);
- sUiBot.assertDatasets("What, me auth?");
+ mUiBot.assertDatasets("What, me auth?");
// Delete again and assert all dataset are shown.
runShellCommand("input keyevent KEYCODE_DEL");
- sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+ mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
// ...and select it this time
final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
- sUiBot.selectDataset(chosenOne);
+ mUiBot.selectDataset(chosenOne);
callback.assertUiHiddenEvent(username);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Check the results.
mActivity.assertAutoFilled();
}
@Test
+ public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
+ fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+ }
+
+ @Test
+ public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception {
+ fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+ }
+
+ @Test
+ public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+ fillDatasetAuthWithClientState(ClientStateLocation.BOTH);
+ }
+
+ private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception {
+ // Set service.
+ enableService();
+
+ // Prepare the authenticated response
+ final CannedDataset dataset = new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .build();
+ final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+ ? AuthenticationActivity.createSender(mContext, 1,
+ dataset)
+ : AuthenticationActivity.createSender(mContext, 1,
+ dataset, newClientState("CSI", "FromIntent"));
+
+ // Configure the service behavior
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .setExtras(newClientState("CSI", "FromResponse"))
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+ .setPresentation(createPresentation("Tap to auth dataset"))
+ .setAuthentication(authentication)
+ .build())
+ .build());
+
+ // Set expectation for the activity
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+
+ // Tap authentication request.
+ mUiBot.selectDataset("Tap to auth dataset");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+
+ // Now trigger save.
+ mActivity.onUsername((v) -> v.setText("malkovich"));
+ mActivity.onPassword((v) -> v.setText("malkovich"));
+ final String expectedMessage = getWelcomeMessage("malkovich");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+ // Assert client state on authentication activity.
+ assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse");
+
+ // Assert client state on save request.
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+ ? "FromResponse" : "FromIntent";
+ assertClientState("on save", saveRequest.data, "CSI", expectedValue);
+ }
+
+ @Test
public void testDisableSelf() throws Exception {
enableService();
@@ -2440,7 +2973,7 @@
}, intentFilter);
// Trigger the negative button.
- sUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT,
+ mUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT,
false, SAVE_DATA_TYPE_PASSWORD);
// Wait for the custom action.
@@ -2489,7 +3022,7 @@
}, intentFilter);
// Trigger the negative button.
- sUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+ mUiBot.saveForAutofill(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
false, SAVE_DATA_TYPE_PASSWORD);
// Wait for the custom action.
@@ -2531,7 +3064,7 @@
// Trigger auto-fill.
mActivity.onUsername(View::requestFocus);
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
final FillRequest fillRequest = sReplier.getNextFillRequest();
@@ -2576,13 +3109,13 @@
mActivity.expectAutoFill("dude", "sweet");
// Explicitly uses the contextual menu to test that functionality.
- sUiBot.getAutofillMenuOption(ID_USERNAME).click();
+ mUiBot.getAutofillMenuOption(ID_USERNAME).click();
final FillRequest fillRequest = sReplier.getNextFillRequest();
- assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
// Should have been automatically filled.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -2626,11 +3159,11 @@
mActivity.forceAutofillOnUsername();
final FillRequest fillRequest = sReplier.getNextFillRequest();
- assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
// Auto-fill it.
- final UiObject2 picker = sUiBot.assertDatasets("The Dude", "Jenny");
- sUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
+ final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
+ mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
// Check the results.
mActivity.assertAutoFilled();
@@ -2657,14 +3190,14 @@
mActivity.forceAutofillOnUsername();
final FillRequest fillRequest = sReplier.getNextFillRequest();
- assertThat(fillRequest.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
// Username value should be available because it triggered the manual request...
assertValue(fillRequest.structure, ID_USERNAME, "dud");
// ... but password didn't
assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
// Selects the dataset.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -2696,7 +3229,7 @@
assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
// Select it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -2719,12 +3252,12 @@
// Assert request.
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
- assertThat(fillRequest2.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
assertValue(fillRequest2.structure, ID_USERNAME, "dude");
assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
// Select it.
- sUiBot.selectDataset("THE DUDE");
+ mUiBot.selectDataset("THE DUDE");
// Check the results.
mActivity.assertAutoFilled();
@@ -2751,12 +3284,12 @@
// Assert request.
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
- assertThat(fillRequest1.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
assertValue(fillRequest1.structure, ID_USERNAME, "");
assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
// Select it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -2779,12 +3312,12 @@
// Assert request.
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
- assertThat(fillRequest2.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
assertValue(fillRequest2.structure, ID_USERNAME, "dude");
assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
// Select it.
- sUiBot.selectDataset("THE DUDE");
+ mUiBot.selectDataset("THE DUDE");
// Check the results.
mActivity.assertAutoFilled();
@@ -2811,7 +3344,7 @@
mActivity.onUsername(View::requestFocus);
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started
@@ -2829,7 +3362,7 @@
mActivity.tapSave();
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -2874,7 +3407,7 @@
sReplier.getNextFillRequest();
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -2925,421 +3458,19 @@
sReplier.getNextFillRequest();
// Click on the custom button
- sUiBot.selectByText("Poke");
+ mUiBot.selectByText("Poke");
// Make sure the click worked
- sUiBot.selectByText("foo");
+ mUiBot.selectByText("foo");
// Go back to the filled app.
- sUiBot.pressBack();
+ mUiBot.pressBack();
// The session should be gone
assertNoDanglingSessions();
}
@Test
- public void checkFillSelectionAfterSelectingDatasetAuthentication() throws Exception {
- enableService();
-
- // Set up FillResponse with dataset authentication
- Bundle clientState = new Bundle();
- clientState.putCharSequence("clientStateKey", "clientStateValue");
-
- // Prepare the authenticated response
- final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "dude")
- .setField(ID_PASSWORD, "sweet")
- .setPresentation(createPresentation("Dataset"))
- .build());
-
- sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "username")
- .setId("name")
- .setPresentation(createPresentation("authentication"))
- .setAuthentication(authentication)
- .build())
- .setExtras(clientState).build());
- mActivity.expectAutoFill("dude", "sweet");
-
- // Trigger autofill.
- mActivity.onUsername(View::requestFocus);
-
- // Authenticate
- sUiBot.selectDataset("authentication");
- sReplier.getNextFillRequest();
- mActivity.assertAutoFilled();
-
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
- "clientStateValue");
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_DATASET_AUTHENTICATION_SELECTED);
- assertThat(event.getDatasetId()).isEqualTo("name");
- }
-
- @Test
- public void checkFillSelectionAfterSelectingAuthentication() throws Exception {
- enableService();
-
- // Set up FillResponse with response wide authentication
- Bundle clientState = new Bundle();
- clientState.putCharSequence("clientStateKey", "clientStateValue");
-
- // Prepare the authenticated response
- final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
- new CannedFillResponse.Builder().addDataset(
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "username")
- .setId("name")
- .setPresentation(createPresentation("dataset"))
- .build())
- .setExtras(clientState).build());
-
- sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
- .setPresentation(createPresentation("authentication"))
- .setAuthentication(authentication, ID_USERNAME)
- .build());
-
- // Trigger autofill.
- mActivity.onUsername(View::requestFocus);
-
- // Authenticate
- sUiBot.selectDataset("authentication");
- sReplier.getNextFillRequest();
- sUiBot.assertDatasets("dataset");
-
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
- "clientStateValue");
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_AUTHENTICATION_SELECTED);
- assertThat(event.getDatasetId()).isNull();
- }
-
- @Test
- public void checkFillSelectionAfterSelectingTwoDatasets() throws Exception {
- enableService();
-
- // Set up first partition with an anonymous dataset
- sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "username")
- .setPresentation(createPresentation("dataset1"))
- .build())
- .build());
- mActivity.expectAutoFill("username");
-
- // Trigger autofill on username
- mActivity.onUsername(View::requestFocus);
- waitUntilConnected();
- sUiBot.selectDataset("dataset1");
- sReplier.getNextFillRequest();
- mActivity.assertAutoFilled();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState()).isNull();
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
- assertThat(event.getDatasetId()).isNull();
- }
-
- // Set up second partition with a named dataset
- Bundle clientState = new Bundle();
- clientState.putCharSequence("clientStateKey", "clientStateValue");
-
- sReplier.addResponse(new CannedFillResponse.Builder()
- .addDataset(
- new CannedDataset.Builder()
- .setField(ID_PASSWORD, "password2")
- .setPresentation(createPresentation("dataset2"))
- .setId("name2")
- .build())
- .addDataset(
- new CannedDataset.Builder()
- .setField(ID_PASSWORD, "password3")
- .setPresentation(createPresentation("dataset3"))
- .setId("name3")
- .build())
- .setExtras(clientState)
- .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
- mActivity.expectPasswordAutoFill("password3");
-
- // Trigger autofill on password
- mActivity.onPassword(View::requestFocus);
- sUiBot.selectDataset("dataset3");
- sReplier.getNextFillRequest();
- mActivity.assertAutoFilled();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
- "clientStateValue");
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
- assertThat(event.getDatasetId()).isEqualTo("name3");
- }
-
- mActivity.onPassword((v) -> v.setText("new password"));
- mActivity.syncRunOnUiThread(() -> mActivity.finish());
- waitUntilDisconnected();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState().getCharSequence("clientStateKey")).isEqualTo(
- "clientStateValue");
-
- assertThat(selection.getEvents().size()).isEqualTo(2);
- FillEventHistory.Event event1 = selection.getEvents().get(0);
- assertThat(event1.getType()).isEqualTo(TYPE_DATASET_SELECTED);
- assertThat(event1.getDatasetId()).isEqualTo("name3");
-
- FillEventHistory.Event event2 = selection.getEvents().get(1);
- assertThat(event2.getType()).isEqualTo(TYPE_SAVE_SHOWN);
- assertThat(event2.getDatasetId()).isNull();
- }
- }
-
- @Test
- public void checkFillSelectionIsResetAfterReturningNull() throws Exception {
- enableService();
-
- // First reset
- sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "username")
- .setPresentation(createPresentation("dataset1"))
- .build())
- .build());
- mActivity.expectAutoFill("username");
-
- mActivity.onUsername(View::requestFocus);
- waitUntilConnected();
- sReplier.getNextFillRequest();
- sUiBot.selectDataset("dataset1");
- mActivity.assertAutoFilled();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState()).isNull();
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
- assertThat(event.getDatasetId()).isNull();
- }
-
- // Second request
- sReplier.addResponse(NO_RESPONSE);
- mActivity.onPassword(View::requestFocus);
- sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
- waitUntilDisconnected();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection).isNull();
- }
- }
-
- @Test
- public void checkFillSelectionIsResetAfterReturningError() throws Exception {
- enableService();
-
- // First reset
- sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "username")
- .setPresentation(createPresentation("dataset1"))
- .build())
- .build());
- mActivity.expectAutoFill("username");
-
- mActivity.onUsername(View::requestFocus);
- waitUntilConnected();
- sReplier.getNextFillRequest();
- sUiBot.selectDataset("dataset1");
- mActivity.assertAutoFilled();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState()).isNull();
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
- assertThat(event.getDatasetId()).isNull();
- }
-
- // Second request
- sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
- mActivity.onPassword(View::requestFocus);
- sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
- waitUntilDisconnected();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection).isNull();
- }
- }
-
- @Test
- public void checkFillSelectionIsResetAfterTimeout() throws Exception {
- enableService();
-
- // First reset
- sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
- new CannedDataset.Builder()
- .setField(ID_USERNAME, "username")
- .setPresentation(createPresentation("dataset1"))
- .build())
- .build());
- mActivity.expectAutoFill("username");
-
- mActivity.onUsername(View::requestFocus);
- waitUntilConnected();
- sReplier.getNextFillRequest();
- sUiBot.selectDataset("dataset1");
- mActivity.assertAutoFilled();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection.getClientState()).isNull();
-
- assertThat(selection.getEvents().size()).isEqualTo(1);
- FillEventHistory.Event event = selection.getEvents().get(0);
- assertThat(event.getType()).isEqualTo(TYPE_DATASET_SELECTED);
- assertThat(event.getDatasetId()).isNull();
- }
-
- // Second request
- sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
- mActivity.onPassword(View::requestFocus);
- sReplier.getNextFillRequest();
- waitUntilDisconnected();
-
- {
- // Verify fill selection
- FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selection).isNull();
- }
- }
-
- private Bundle getBundle(String key, String value) {
- final Bundle bundle = new Bundle();
- bundle.putString(key, value);
- return bundle;
- }
-
- /**
- * Tests the following scenario:
- *
- * <ol>
- * <li>Activity A is launched.
- * <li>Activity A triggers autofill.
- * <li>Activity B is launched.
- * <li>Activity B triggers autofill.
- * <li>User goes back to Activity A.
- * <li>User triggers save on Activity A - at this point, service should have stats of
- * activity B, and stats for activity A should have beeen discarded.
- * </ol>
- */
- @Test
- public void checkFillSelectionFromPreviousSessionIsDiscarded() throws Exception {
- enableService();
-
- // Launch activity A
- sReplier.addResponse(new CannedFillResponse.Builder()
- .setExtras(getBundle("activity", "A"))
- .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
- .build());
-
- // Trigger autofill on activity A
- mActivity.onUsername(View::requestFocus);
- waitUntilConnected();
- sReplier.getNextFillRequest();
-
- // Verify fill selection for Activity A
- FillEventHistory selectionA = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selectionA.getClientState().getString("activity")).isEqualTo("A");
- assertThat(selectionA.getEvents()).isNull();
-
- // Launch activity B
- mContext.startActivity(new Intent(mContext, CheckoutActivity.class));
-
- // Trigger autofill on activity B
- sReplier.addResponse(new CannedFillResponse.Builder()
- .setExtras(getBundle("activity", "B"))
- .addDataset(new CannedDataset.Builder()
- .setField(ID_CC_NUMBER, "4815162342")
- .setPresentation(createPresentation("datasetB"))
- .build())
- .build());
- sUiBot.focusByRelativeId(ID_CC_NUMBER);
- sReplier.getNextFillRequest();
-
- // Verify fill selection for Activity B
- final FillEventHistory selectionB = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(selectionB.getClientState().getString("activity")).isEqualTo("B");
- assertThat(selectionB.getEvents()).isNull();
-
- // Now switch back to A...
- sUiBot.pressBack(); // dismiss keyboard
- sUiBot.pressBack(); // dismiss task
- sUiBot.assertShownByRelativeId(ID_USERNAME);
- // ...and trigger save
- // Set credentials...
- mActivity.onUsername((v) -> v.setText("malkovich"));
- mActivity.onPassword((v) -> v.setText("malkovich"));
- final String expectedMessage = getWelcomeMessage("malkovich");
- final String actualMessage = mActivity.tapLogin();
- assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
- sReplier.getNextSaveRequest();
-
- // Finally, make sure history is right
- final FillEventHistory finalSelection = InstrumentedAutoFillService.peekInstance()
- .getFillEventHistory();
- assertThat(finalSelection.getClientState().getString("activity")).isEqualTo("B");
- assertThat(finalSelection.getEvents()).isNull();
-
- }
-
- @Test
public void testIsServiceEnabled() throws Exception {
disableService();
final AutofillManager afm = mActivity.getAutofillManager();
@@ -3353,6 +3484,19 @@
}
@Test
+ public void testGetAutofillServiceComponentName() throws Exception {
+ final AutofillManager afm = mActivity.getAutofillManager();
+
+ enableService();
+ final ComponentName componentName = afm.getAutofillServiceComponentName();
+ assertThat(componentName.getPackageName()).isEqualTo(SERVICE_PACKAGE);
+ assertThat(componentName.getClassName()).endsWith(SERVICE_CLASS);
+
+ disableService();
+ assertThat(afm.getAutofillServiceComponentName()).isNull();
+ }
+
+ @Test
public void testSetupComplete() throws Exception {
enableService();
@@ -3385,13 +3529,52 @@
// Trigger auto-fill.
mActivity.onUsername(View::requestFocus);
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("The Dude");
+ mUiBot.assertDatasets("The Dude");
// Now disable service by setting another service
Helper.enableAutofillService(mContext, NoOpAutofillService.SERVICE_NAME);
// ...and make sure popup's gone
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
+ }
+
+ @Test
+ public void testServiceIsDisabledWhenNewServiceInfoIsInvalid() throws Exception {
+ serviceIsDisabledWhenNewServiceIsInvalid(BadAutofillService.SERVICE_NAME);
+ }
+
+ @Test
+ public void testServiceIsDisabledWhenNewServiceNameIsInvalid() throws Exception {
+ serviceIsDisabledWhenNewServiceIsInvalid("Y_U_NO_VALID");
+ }
+
+ private void serviceIsDisabledWhenNewServiceIsInvalid(String serviceName) throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Dude"))
+ .build());
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger autofill.
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("The Dude");
+
+ // Now disable service by setting another service...
+ Helper.enableAutofillService(mContext, serviceName);
+
+ // ...and make sure popup's gone
+ mUiBot.assertNoDatasets();
+
+ // Then try to trigger autofill again...
+ mActivity.onPassword(View::requestFocus);
+ //...it should not work!
+ mUiBot.assertNoDatasets();
}
@Test
@@ -3412,7 +3595,7 @@
sReplier.getNextFillRequest();
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -3465,7 +3648,7 @@
// Make sure all datasets are shown.
// TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
// shown
- sUiBot.assertDatasets("DS-1", "DS-2", "DS-3");
+ mUiBot.assertDatasets("DS-1", "DS-2", "DS-3");
// TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
}
@@ -3477,7 +3660,7 @@
// Set expectations.
final OneTimeCancellationSignalListener listener =
- new OneTimeCancellationSignalListener(Helper.FILL_TIMEOUT_MS + 2000);
+ new OneTimeCancellationSignalListener(Timeouts.FILL_TIMEOUT.ms() + 2000);
sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
// Trigger auto-fill.
@@ -3491,4 +3674,27 @@
waitUntilDisconnected();
listener.assertOnCancelCalled();
}
+
+ @Test
+ public void testNewTextAttributes() throws Exception {
+ enableService();
+ sReplier.addResponse(NO_RESPONSE);
+ mActivity.onUsername(View::requestFocus);
+
+ final FillRequest request = sReplier.getNextFillRequest();
+ final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
+ assertThat(username.getMinTextEms()).isEqualTo(2);
+ assertThat(username.getMaxTextEms()).isEqualTo(5);
+ assertThat(username.getMaxTextLength()).isEqualTo(25);
+
+ final ViewNode container = findNodeByResourceId(request.structure, ID_USERNAME_CONTAINER);
+ assertThat(container.getMinTextEms()).isEqualTo(-1);
+ assertThat(container.getMaxTextEms()).isEqualTo(-1);
+ assertThat(container.getMaxTextLength()).isEqualTo(-1);
+
+ final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
+ assertThat(password.getMinTextEms()).isEqualTo(-1);
+ assertThat(password.getMaxTextEms()).isEqualTo(-1);
+ assertThat(password.getMaxTextLength()).isEqualTo(-1);
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
new file mode 100644
index 0000000..90c3e93
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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.autofillservice.cts;
+
+/**
+ * Same as {@link LoginActivity}, but with the texts for some fields set from resources.
+ */
+public class LoginWithStringsActivity extends LoginActivity {
+
+ @Override
+ protected int getContentView() {
+ return R.layout.login_with_strings_activity;
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
new file mode 100644
index 0000000..73ab7d9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.assertTextFromResouces;
+import static android.autofillservice.cts.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
+import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.os.Bundle;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class LoginWithStringsActivityTest extends AutoFillServiceTestCase {
+
+ @Rule
+ public final AutofillActivityTestRule<LoginWithStringsActivity> mActivityRule =
+ new AutofillActivityTestRule<LoginWithStringsActivity>(LoginWithStringsActivity.class);
+
+ private LoginWithStringsActivity mActivity;
+
+ @Before
+ public void setActivity() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @Test
+ public void testAutoFillOneDatasetAndSave() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final Bundle extras = new Bundle();
+ extras.putString("numbers", "4815162342");
+
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setId("I'm the alpha and the omega")
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Dude"))
+ .build())
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+ .setExtras(extras)
+ .build());
+ mActivity.expectAutoFill("dude", "sweet");
+
+ // Trigger auto-fill.
+ mActivity.onUsername(View::requestFocus);
+ waitUntilConnected();
+
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+ // Make sure input was sanitized.
+ assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+ assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+ // Make sure labels were not sanitized
+ assertTextFromResouces(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
+ "username_string");
+ assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+ "password_string");
+
+ // Auto-fill it.
+ mUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+
+ // Try to login, it will fail.
+ final String loginMessage = mActivity.tapLogin();
+
+ assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
+
+ // Set right password...
+ mActivity.onPassword((v) -> v.setText("dude"));
+
+ // ... and try again
+ final String expectedMessage = getWelcomeMessage("dude");
+ final String actualMessage = mActivity.tapLogin();
+ assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+ // Assert the snack bar is shown and tap "Save".
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+ assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
+ // Assert value of expected fields - should not be sanitized.
+ final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+ assertTextAndValue(username, "dude");
+ final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+ assertTextAndValue(password, "dude");
+
+ // Make sure labels were not sanitized
+ assertTextFromResouces(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
+ "username_string");
+ assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+ "password_string");
+
+ // Make sure extras were passed back on onSave()
+ assertThat(saveRequest.data).isNotNull();
+ final String extraValue = saveRequest.data.getString("numbers");
+ assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleExceptionsCatcher.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleExceptionsCatcher.java
deleted file mode 100644
index 52c3bcc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleExceptionsCatcher.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper used to catch multiple exceptions that might have happened in a test case.
- */
-// TODO: move to common CTS code (and add test cases to it)
-public final class MultipleExceptionsCatcher {
-
- private static final String TAG = "MultipleExceptionsCatcher";
-
- private final List<Throwable> mThrowables = new ArrayList<>();
-
- /**
- * Runs {@code r} postponing any thrown exception to {@link #throwIfAny()}.
- */
- public MultipleExceptionsCatcher run(@NonNull Runnable r) {
- try {
- r.run();
- } catch (Throwable t) {
- mThrowables.add(t);
- }
- return this;
- }
-
- /**
- * Adds an exception - if it's not {@code null} to the exceptions thrown by
- * {@link #throwIfAny()}.
- */
- public MultipleExceptionsCatcher add(@Nullable Throwable t) {
- if (t != null) {
- mThrowables.add(t);
- }
- return this;
- }
-
- /**
- * Throws one exception merging all exceptions thrown or added so far, if any.
- */
- public void throwIfAny() throws Throwable {
- if (mThrowables.isEmpty()) return;
-
- final int numberExceptions = mThrowables.size();
- if (numberExceptions == 1) {
- throw mThrowables.get(0);
- }
-
- String msg = "D'OH!";
- try {
- try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
- sw.write("Caught " + numberExceptions + " exceptions\n");
- for (int i = 0; i < numberExceptions; i++) {
- sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
- final Throwable exception = mThrowables.get(i);
- exception.printStackTrace(pw);
- sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
- }
- msg = sw.toString();
- }
- } catch (IOException e) {
- // ignore close() errors - should not happen...
- Log.e(TAG, "Exception closing StringWriter: " + e);
- }
- throw new AssertionError(msg);
- }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
index 3e30b9b..8da4add 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
@@ -18,8 +18,6 @@
import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.eventually;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
@@ -70,18 +68,12 @@
new InstrumentedAutoFillService.FillRequest[1];
// Trigger autofill
- eventually(() -> {
- mActivity.syncRunOnUiThread(() -> {
- mEditText2.requestFocus();
- mEditText1.requestFocus();
- });
+ mActivity.syncRunOnUiThread(() -> {
+ mEditText2.requestFocus();
+ mEditText1.requestFocus();
+ });
- try {
- fillRequest[0] = sReplier.getNextFillRequest();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }, (int) (FILL_TIMEOUT_MS * 2));
+ fillRequest[0] = sReplier.getNextFillRequest();
assertThat(fillRequest[0].data).isNull();
@@ -94,8 +86,8 @@
assertThat(findNodeByResourceId(structure, "editText5")).isNull();
// Wait until autofill has been applied
- sUiBot.selectDataset("dataset1");
- sUiBot.assertShownByText("editText1-autofilled");
+ mUiBot.selectDataset("dataset1");
+ mUiBot.assertShownByText("editText1-autofilled");
// Manually fill view
mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled"));
@@ -153,16 +145,16 @@
assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull();
// Wait until autofill has been applied
- sUiBot.selectDataset("dataset2");
- sUiBot.assertShownByText("editText3-autofilled");
- sUiBot.assertShownByText("editText4-autofilled");
+ mUiBot.selectDataset("dataset2");
+ mUiBot.assertShownByText("editText3-autofilled");
+ mUiBot.assertShownByText("editText4-autofilled");
// Manually fill view
mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled"));
// Finish activity and save data
mActivity.finish();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
// The saveRequest should have a fillContext for each partition with all the data
InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -229,7 +221,7 @@
// Check UI is shown, but don't select it.
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("dataset1");
+ mUiBot.assertDatasets("dataset1");
// Switch fragments
sReplier.addResponse(NO_RESPONSE);
@@ -239,7 +231,7 @@
FRAGMENT_TAG).commitNow());
// Make sure UI is gone.
sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
}
// TODO: add similar tests for fragment with virtual view
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
index b264a46..5af2762 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -26,7 +26,7 @@
import java.util.concurrent.TimeUnit;
/**
- * Custom {@link RadioGroup.OnCheckedChangeListener} used to assert an
+ * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
* {@link RadioGroup} was auto-filled properly.
*/
final class MultipleTimesRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
@@ -49,8 +49,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = mLatch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT_MS, mName)
+ final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), mName)
.that(set).isTrue();
final int actual = mRadioGroup.getAutofillValue().getListValue();
assertWithMessage("Wrong auto-fill value on RadioGroup %s", mName)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
index c928f31..a510639 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -62,8 +62,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = mLatch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on EditText %s", FILL_TIMEOUT_MS, mName)
+ final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on EditText %s", FILL_TIMEOUT.ms(), mName)
.that(set).isTrue();
final String actual = mEditText.getText().toString();
assertWithMessage("Wrong auto-fill value on EditText %s", mName)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
index 1aed119..2519aec 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -50,8 +50,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT_MS, name)
+ final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT.ms(), name)
.that(set).isTrue();
assertWithMessage("Wrong hour on TimePicker %s", name)
.that(timePicker.getHour()).isEqualTo(expectedHour);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
index 1ba8755..3d6acc9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
@@ -16,10 +16,11 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.CONNECTION_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillManager.AutofillCallback;
@@ -32,15 +33,18 @@
*/
final class MyAutofillCallback extends AutofillCallback {
+ private static final String TAG = "MyAutofillCallback";
private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
@Override
public void onAutofillEvent(View view, int event) {
+ Log.v(TAG, "onAutofillEvent: view=" + view + ", event=" + event);
mEvents.offer(new MyEvent(view, event));
}
@Override
public void onAutofillEvent(View view, int childId, int event) {
+ Log.v(TAG, "onAutofillEvent: view=" + view + ", child=" + childId + ", event=" + event);
mEvents.offer(new MyEvent(view, childId, event));
}
@@ -48,14 +52,25 @@
* Gets the next available event or fail if it times out.
*/
MyEvent getEvent() throws InterruptedException {
- final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
if (event == null) {
- throw new RetryableException("no event in %d ms", CONNECTION_TIMEOUT_MS);
+ throw new RetryableException(CONNECTION_TIMEOUT, "no event");
}
return event;
}
/**
+ * Assert no more events were received.
+ */
+ void assertNotCalled() throws InterruptedException {
+ final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ if (event != null) {
+ // Not retryable.
+ throw new IllegalStateException("should not have received " + event);
+ }
+ }
+
+ /**
* Used to assert there is no event left behind.
*/
void assertNumberUnhandledEvents(int expected) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
new file mode 100644
index 0000000..9cddac6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.autofill.AutofillValue;
+import android.webkit.WebView;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link WebView} used to assert contents were autofilled.
+ */
+public class MyWebView extends WebView {
+
+ private FillExpectation mExpectation;
+
+ public MyWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void expectAutofill(String username, String password) {
+ mExpectation = new FillExpectation(username, password);
+ }
+
+ public void assertAutofilled() throws Exception {
+ assertWithMessage("expectAutofill() not called").that(mExpectation).isNotNull();
+ final boolean set = mExpectation.mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ if (mExpectation.mException != null) {
+ throw mExpectation.mException;
+ }
+ assertWithMessage("Timeout (%s ms) expecting autofill()", FILL_TIMEOUT.ms())
+ .that(set).isTrue();
+ assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
+ .isEqualTo(mExpectation.mExpectedUsername);
+ assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
+ .isEqualTo(mExpectation.mExpectedPassword);
+ }
+
+ @Override
+ public void autofill(SparseArray<AutofillValue> values) {
+ super.autofill(values);
+
+ if (mExpectation == null) return;
+
+ try {
+ if (values == null || values.size() != 2) {
+ mExpectation.mException =
+ new IllegalArgumentException("Invalid values on autofill(): " + values);
+ } else {
+ try {
+ // We don't know the order of the values in the array. As we're just expecting
+ // 2, it's easy to just check them individually; if we had more, than we would
+ // need to override onProvideAutofillVirtualStructure() to keep track of the
+ // nodes added by WebView so we could save their AutofillIds and reuse here.
+ final String value1 = values.valueAt(0).getTextValue().toString();
+ final String value2 = values.valueAt(1).getTextValue().toString();
+ if (mExpectation.mExpectedUsername.equals(value1)) {
+ mExpectation.mActualUsername = value1;
+ mExpectation.mActualPassword = value2;
+ } else {
+ mExpectation.mActualUsername = value2;
+ mExpectation.mActualPassword = value1;
+ }
+ } catch (Exception e) {
+ mExpectation.mException = e;
+ }
+ }
+ } finally {
+ mExpectation.mLatch.countDown();
+ }
+ }
+
+ private class FillExpectation {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mExpectedUsername;
+ private final String mExpectedPassword;
+ private String mActualUsername;
+ private String mActualPassword;
+ private Exception mException;
+
+ FillExpectation(String username, String password) {
+ this.mExpectedUsername = username;
+ this.mExpectedPassword = password;
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
index 4255b6d..a75ded8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
@@ -21,21 +21,26 @@
import android.service.autofill.FillRequest;
import android.service.autofill.SaveCallback;
import android.service.autofill.SaveRequest;
+import android.util.Log;
/**
* {@link AutofillService} implementation that does not do anything...
*/
public class NoOpAutofillService extends AutofillService {
+ private static final String TAG = "NoOpAutofillService";
+
static final String SERVICE_NAME = NoOpAutofillService.class.getPackage().getName()
+ "/." + NoOpAutofillService.class.getSimpleName();
@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback) {
+ Log.d(TAG, "onFillRequest()");
}
@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.d(TAG, "onFillResponse()");
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
index 071dec6..4d7af94 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -48,8 +48,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT_MS, name)
+ final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT.ms(), name)
.that(set).isTrue();
final boolean actual = button.isChecked();
assertWithMessage("Wrong auto-fill value on CompoundButton %s", name)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
index ef28a23..407861d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -51,8 +51,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT_MS, name)
+ final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT.ms(), name)
.that(set).isTrue();
assertWithMessage("Wrong year on DatePicker %s", name)
.that(datePicker.getYear()).isEqualTo(expectedYear);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
index 1903cb9..73ed648 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -47,8 +47,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT_MS, name)
+ final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), name)
.that(set).isTrue();
final int actual = radioGroup.getCheckedRadioButtonId();
assertWithMessage("Wrong auto-fill value on RadioGroup %s", name)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSettingsListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSettingsListener.java
deleted file mode 100644
index e723357..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSettingsListener.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package android.autofillservice.cts;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper used to block tests until a secure settings value has been updated.
- */
-final class OneTimeSettingsListener extends ContentObserver {
- private final CountDownLatch mLatch = new CountDownLatch(1);
- private final ContentResolver mResolver;
- private final String mKey;
-
- public OneTimeSettingsListener(Context context, String key) {
- super(new Handler(Looper.getMainLooper()));
- mKey = key;
- mResolver = context.getContentResolver();
- mResolver.registerContentObserver(Settings.Secure.getUriFor(key), false, this);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- mResolver.unregisterContentObserver(this);
- mLatch.countDown();
- }
-
- /**
- * Blocks for a few seconds until it's called.
- *
- * @throws IllegalStateException if it's not called.
- */
- void assertCalled() {
- try {
- final boolean updated = mLatch.await(5, TimeUnit.SECONDS);
- if (!updated) {
- throw new IllegalStateException("Settings " + mKey + " not called in 5s");
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IllegalStateException("Interrupted", e);
- }
- }
-}
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
index 6bc8279..5fb5973 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -44,8 +44,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT_MS, name)
+ final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT.ms(), name)
.that(set).isTrue();
final int actual = spinner.getSelectedItemPosition();
assertWithMessage("Wrong auto-fill value on Spinner %s", name)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
index c82fa42..fb169d0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
@@ -32,7 +32,6 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -64,11 +63,6 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
/**
* Creates a standard builder common to all tests.
*/
@@ -122,7 +116,7 @@
mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started.
@@ -135,7 +129,7 @@
mActivity.save();
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
@@ -182,7 +176,7 @@
mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
// Sanity check.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Wait for onFill() before proceeding, otherwise the fields might be changed before
// the session started.
@@ -195,7 +189,7 @@
mActivity.save();
// Assert the snack bar is shown and tap "Save".
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
// Once saved, the session should be finsihed.
assertNoDanglingSessions();
@@ -320,7 +314,7 @@
sReplier.getNextFillRequest();
// Auto-fill it.
- sUiBot.selectDataset("Da Dataset");
+ mUiBot.selectDataset("Da Dataset");
// Check the results.
mActivity.assertAutoFilled();
@@ -332,7 +326,7 @@
mActivity.save();
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
@@ -419,7 +413,7 @@
sReplier.getNextFillRequest();
// Auto-fill it.
- sUiBot.selectDataset("Da Dataset");
+ mUiBot.selectDataset("Da Dataset");
// Check the results.
mActivity.assertAutoFilled();
@@ -431,7 +425,7 @@
mActivity.save();
// Assert the snack bar is not shown.
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
// Once saved, the session should be finsihed.
assertNoDanglingSessions();
@@ -585,16 +579,16 @@
// Make sure the snack bar is not shown.
if (expectSaveUi) {
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
} else {
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
}
// ...then tap save.
mActivity.save();
// Assert the snack bar is not shown.
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
}
@Test
@@ -621,7 +615,7 @@
mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
sReplier.getNextFillRequest();
- sUiBot.selectDataset("SF");
+ mUiBot.selectDataset("SF");
mActivity.assertAutoFilled();
// Clear the field.
@@ -631,7 +625,7 @@
mActivity.save();
// ...and make sure the snack bar is not shown.
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
}
@Test
@@ -658,7 +652,7 @@
mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
sReplier.getNextFillRequest();
- sUiBot.selectDataset("SF");
+ mUiBot.selectDataset("SF");
mActivity.assertAutoFilled();
// Clear the field.
@@ -668,7 +662,7 @@
mActivity.save();
// ...and make sure the snack bar is shown.
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
// Finally, assert values.
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
index 83d1ce2..d37cca6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
@@ -45,6 +45,19 @@
}
@Override
+ protected void onStart() {
+ Log.i(LOG_TAG, "onStart()");
+ super.onStart();
+ try {
+ if (!getStartedMarker(this).createNewFile()) {
+ Log.e(LOG_TAG, "cannot write started file");
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "cannot write started file");
+ }
+ }
+
+ @Override
protected void onStop() {
Log.i(LOG_TAG, "onStop()");
super.onStop();
@@ -71,4 +84,14 @@
@NonNull public static File getStoppedMarker(@NonNull Context context) {
return new File(context.getFilesDir(), "stopped");
}
+
+ /**
+ * Get the file that signals that the activity has entered {@link Activity#onStart()}.
+ *
+ * @param context Context of the app
+ * @return The marker file that is written onStart()
+ */
+ @NonNull public static File getStartedMarker(@NonNull Context context) {
+ return new File(context.getFilesDir(), "started");
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
index d458940..1882ed8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
@@ -23,6 +23,8 @@
import static android.autofillservice.cts.GridActivity.ID_L3C2;
import static android.autofillservice.cts.GridActivity.ID_L4C1;
import static android.autofillservice.cts.GridActivity.ID_L4C2;
+import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.Helper.assertHasFlags;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertValue;
import static android.autofillservice.cts.Helper.getContext;
@@ -46,12 +48,13 @@
import android.content.IntentSender;
import android.os.Bundle;
import android.service.autofill.FillResponse;
-import android.widget.RemoteViews;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.TimeoutException;
+
/**
* Test case for an activity containing multiple partitions.
*/
@@ -68,6 +71,30 @@
mActivity = mActivityRule.getActivity();
}
+ /**
+ * Focus to a cell and expect window event
+ */
+ void focusCell(int row, int column) throws TimeoutException {
+ mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
+ Timeouts.UI_TIMEOUT.getMaxValue());
+ }
+
+ /**
+ * Focus to a cell and expect no window event.
+ */
+ void focusCellNoWindowChange(int row, int column) {
+ try {
+ // TODO: define a small value in Timeout
+ mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
+ Timeouts.UI_TIMEOUT.ms());
+ } catch (TimeoutException ex) {
+ // no window events! looking good
+ return;
+ }
+ throw new IllegalStateException(String.format("Expect no window event when focusing to"
+ + " column %d row %d, but event happened", row, column));
+ }
+
@Test
public void testAutofillTwoPartitionsSkipFirst() throws Exception {
// Set service.
@@ -83,7 +110,7 @@
sReplier.addResponse(response1);
// Trigger auto-fill on 1st partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.flags).isEqualTo(0);
final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
@@ -92,9 +119,9 @@
assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
// Make sure UI is shown, but don't tap it.
- sUiBot.assertDatasets("l1c1");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("l1c2");
+ mUiBot.assertDatasets("l1c1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("l1c2");
// Now tap a field in a different partition
final CannedFillResponse response2 = new CannedFillResponse.Builder()
@@ -106,7 +133,7 @@
sReplier.addResponse(response2);
// Trigger auto-fill on 2nd partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
assertThat(fillRequest2.flags).isEqualTo(0);
final ViewNode p2l1c1 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
@@ -118,16 +145,16 @@
assertWithMessage("Focus on p2l2c1").that(p2l2c1.isFocused()).isTrue();
assertWithMessage("Focus on p2l2c2").that(p2l2c2.isFocused()).isFalse();
// Make sure UI is shown, but don't tap it.
- sUiBot.assertDatasets("l2c1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("l2c2");
+ mUiBot.assertDatasets("l2c1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("l2c2");
// Now fill them
final FillExpectation expectation1 = mActivity.expectAutofill()
.onCell(1, 1, "l1c1")
.onCell(1, 2, "l1c2");
- mActivity.focusCell(1, 1);
- sUiBot.selectDataset("l1c1");
+ focusCell(1, 1);
+ mUiBot.selectDataset("l1c1");
expectation1.assertAutoFilled();
// Change previous values to make sure they are not filled again
@@ -137,8 +164,8 @@
final FillExpectation expectation2 = mActivity.expectAutofill()
.onCell(2, 1, "l2c1")
.onCell(2, 2, "l2c2");
- mActivity.focusCell(2, 2);
- sUiBot.selectDataset("l2c2");
+ focusCell(2, 2);
+ mUiBot.selectDataset("l2c2");
expectation2.assertAutoFilled();
// Make sure previous partition didn't change
@@ -166,7 +193,7 @@
.onCell(1, 2, "l1c2");
// Trigger auto-fill.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.flags).isEqualTo(0);
@@ -174,7 +201,7 @@
assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 1");
+ mUiBot.selectDataset("Partition 1");
// Check the results.
expectation1.assertAutoFilled();
@@ -194,7 +221,7 @@
.onCell(2, 2, "l2c2");
// Trigger auto-fill.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
assertThat(fillRequest2.flags).isEqualTo(0);
@@ -204,7 +231,7 @@
assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 2");
+ mUiBot.selectDataset("Partition 2");
// Check the results.
expectation2.assertAutoFilled();
@@ -253,7 +280,7 @@
assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 1");
+ mUiBot.selectDataset("Partition 1");
// Check the results.
expectation1.assertAutoFilled();
@@ -287,7 +314,7 @@
assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 2");
+ mUiBot.selectDataset("Partition 2");
// Check the results.
expectation2.assertAutoFilled();
@@ -323,7 +350,7 @@
assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 3");
+ mUiBot.selectDataset("Partition 3");
// Check the results.
expectation3.assertAutoFilled();
@@ -361,7 +388,7 @@
assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 4");
+ mUiBot.selectDataset("Partition 4");
// Check the results.
expectation4.assertAutoFilled();
@@ -387,7 +414,7 @@
.onCell(1, 2, "l1c2");
// Trigger auto-fill.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.flags).isEqualTo(0);
@@ -395,7 +422,7 @@
assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 1");
+ mUiBot.selectDataset("Partition 1");
// Check the results.
expectation1.assertAutoFilled();
@@ -420,7 +447,7 @@
// Trigger auto-fill.
mActivity.forceAutofill(2, 1);
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
- assertThat(fillRequest2.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
@@ -428,7 +455,7 @@
assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 2");
+ mUiBot.selectDataset("Partition 2");
// Check the results.
expectation2.assertAutoFilled();
@@ -448,7 +475,7 @@
.onCell(3, 2, "l3c2");
// Trigger auto-fill.
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
final FillRequest fillRequest3 = sReplier.getNextFillRequest();
assertThat(fillRequest3.flags).isEqualTo(0);
@@ -460,7 +487,7 @@
assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 3");
+ mUiBot.selectDataset("Partition 3");
// Check the results.
expectation3.assertAutoFilled();
@@ -484,7 +511,7 @@
// Trigger auto-fill.
mActivity.forceAutofill(4, 1);
final FillRequest fillRequest4 = sReplier.getNextFillRequest();
- assertThat(fillRequest4.flags).isEqualTo(FLAG_MANUAL_REQUEST);
+ assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
@@ -496,7 +523,7 @@
assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
// Auto-fill it.
- sUiBot.selectDataset("Partition 4");
+ mUiBot.selectDataset("Partition 4");
// Check the results.
expectation4.assertAutoFilled();
@@ -521,11 +548,11 @@
sReplier.addResponse(response1);
// Trigger auto-fill on 1st partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.flags).isEqualTo(0);
assertThat(fillRequest1.data).isNull();
- sUiBot.assertDatasets("l1c1");
+ mUiBot.assertDatasets("l1c1");
// Prepare 2nd partition; it replaces 'number' and adds 'numbers2'
extras.clear();
@@ -542,7 +569,7 @@
sReplier.addResponse(response2);
// Trigger auto-fill on 2nd partition
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
assertThat(fillRequest2.flags).isEqualTo(0);
assertWithMessage("null bundle on request 2").that(fillRequest2.data).isNotNull();
@@ -561,7 +588,7 @@
sReplier.addResponse(response3);
// Trigger auto-fill on 3rd partition
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
final FillRequest fillRequest3 = sReplier.getNextFillRequest();
assertThat(fillRequest3.flags).isEqualTo(0);
assertWithMessage("null bundle on request 3").that(fillRequest2.data).isNotNull();
@@ -586,7 +613,7 @@
sReplier.addResponse(response4);
// Trigger auto-fill on 4th partition
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
final FillRequest fillRequest4 = sReplier.getNextFillRequest();
assertThat(fillRequest4.flags).isEqualTo(0);
assertWithMessage("non-null bundle on request 4").that(fillRequest4.data).isNull();
@@ -595,7 +622,7 @@
mActivity.setText(1, 1, "L1C1");
mActivity.save();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertWithMessage("wrong number of extras on save request bundle")
@@ -616,7 +643,7 @@
.build())
.build();
sReplier.addResponse(response1);
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Trigger 2nd partition.
@@ -628,14 +655,14 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L2C1)
.build();
sReplier.addResponse(response2);
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Trigger save
mActivity.setText(2, 1, "L2C1");
mActivity.save();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertValue(saveRequest.structure, ID_L2C1, "L2C1");
}
@@ -653,7 +680,7 @@
.build())
.build();
sReplier.addResponse(response1);
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Trigger 2nd partition.
@@ -665,14 +692,14 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
.build();
sReplier.addResponse(response2);
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Trigger save
mActivity.setText(1, 1, "L1C1");
mActivity.save();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertValue(saveRequest.structure, ID_L1C1, "L1C1");
}
@@ -691,7 +718,7 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
.build();
sReplier.addResponse(response1);
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Trigger 2nd partition.
@@ -704,7 +731,7 @@
ID_L2C1)
.build();
sReplier.addResponse(response2);
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Trigger save
@@ -712,7 +739,7 @@
mActivity.setText(2, 1, "L2C1");
mActivity.save();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertValue(saveRequest.structure, ID_L1C1, "L1C1");
assertValue(saveRequest.structure, ID_L2C1, "L2C1");
@@ -732,7 +759,7 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
.build();
sReplier.addResponse(response1);
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Trigger 2nd partition.
@@ -745,7 +772,7 @@
ID_L2C1)
.build();
sReplier.addResponse(response2);
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Trigger 3rd partition.
@@ -758,7 +785,7 @@
| SAVE_DATA_TYPE_USERNAME, ID_L3C1)
.build();
sReplier.addResponse(response3);
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
// Trigger save
@@ -767,7 +794,7 @@
mActivity.setText(3, 1, "L3C1");
mActivity.save();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
SAVE_DATA_TYPE_USERNAME);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertValue(saveRequest.structure, ID_L1C1, "L1C1");
@@ -789,7 +816,7 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
.build();
sReplier.addResponse(response1);
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Trigger 2nd partition.
@@ -801,7 +828,7 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC, ID_L2C1)
.build();
sReplier.addResponse(response2);
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Trigger 3rd partition.
@@ -815,7 +842,7 @@
ID_L3C1)
.build();
sReplier.addResponse(response3);
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
@@ -826,7 +853,7 @@
mActivity.save();
// Make sure GENERIC type is not shown on snackbar
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertValue(saveRequest.structure, ID_L1C1, "L1C1");
assertValue(saveRequest.structure, ID_L2C1, "L2C1");
@@ -847,7 +874,7 @@
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
.build();
sReplier.addResponse(response1);
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Trigger 2nd partition.
@@ -860,7 +887,7 @@
ID_L2C1)
.build();
sReplier.addResponse(response2);
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Trigger 3rd partition.
@@ -873,7 +900,7 @@
| SAVE_DATA_TYPE_USERNAME, ID_L3C1)
.build();
sReplier.addResponse(response3);
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
// Trigger 4th partition.
@@ -886,7 +913,7 @@
| SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_ADDRESS, ID_L4C1)
.build();
sReplier.addResponse(response4);
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
@@ -897,7 +924,7 @@
mActivity.setText(4, 1, "L4C1");
mActivity.save();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertValue(saveRequest.structure, ID_L1C1, "L1C1");
assertValue(saveRequest.structure, ID_L2C1, "L2C1");
@@ -921,7 +948,7 @@
sReplier.addResponse(response1);
// Trigger auto-fill on 1st partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.flags).isEqualTo(0);
final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
@@ -930,15 +957,15 @@
assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
// Make sure UI is shown on 1st partition
- sUiBot.assertDatasets("l1c1");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("l1c2");
+ mUiBot.assertDatasets("l1c1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("l1c2");
// Make sure UI is not shown on ignored partition
- mActivity.focusCell(2, 1);
- sUiBot.assertNoDatasets();
- mActivity.focusCell(2, 2);
- sUiBot.assertNoDatasets();
+ focusCell(2, 1);
+ mUiBot.assertNoDatasets();
+ focusCellNoWindowChange(2, 2);
+ mUiBot.assertNoDatasets();
}
/**
@@ -971,7 +998,7 @@
.onCell(1, 2, "l1c2");
// Trigger partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
@@ -995,7 +1022,7 @@
.onCell(2, 2, "L2C2");
// Trigger partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
/**
@@ -1020,7 +1047,7 @@
.onCell(3, 2, "L3C2");
// Trigger partition.
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
/**
@@ -1043,49 +1070,49 @@
.onCell(4, 1, "l4c1");
// Trigger partition.
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
/*
* Now move focus around to make sure the proper values are displayed each time.
*/
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P1D1", "P1D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P1D1", "P1D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P2D1", "P2D2");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P2D1", "P2D2");
- mActivity.focusCell(4, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("P4D2");
+ focusCell(4, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("P4D2");
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P3D1", "P3D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P3D1", "P3D2");
/*
* Finally, autofill and check results.
*/
- mActivity.focusCell(4, 1);
- sUiBot.selectDataset("P4D1");
+ focusCell(4, 1);
+ mUiBot.selectDataset("P4D1");
expectation4.assertAutoFilled();
- mActivity.focusCell(1, 1);
- sUiBot.selectDataset("P1D1");
+ focusCell(1, 1);
+ mUiBot.selectDataset("P1D1");
expectation1.assertAutoFilled();
- mActivity.focusCell(3, 1);
- sUiBot.selectDataset("P3D2");
+ focusCell(3, 1);
+ mUiBot.selectDataset("P3D2");
expectation3.assertAutoFilled();
- mActivity.focusCell(2, 2);
- sUiBot.selectDataset("P2D2");
+ focusCell(2, 2);
+ mUiBot.selectDataset("P2D2");
expectation2.assertAutoFilled();
}
@@ -1141,14 +1168,13 @@
sReplier.addResponse(response1);
// Trigger partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P1D1", "P1D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
+ mUiBot.assertDatasets("P1D1", "P1D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
/**
* 2nd partition.
@@ -1169,18 +1195,18 @@
sReplier.addResponse(response2);
// Trigger partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P2D1"); // changed
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P2D1", "P2D2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P2D1"); // changed
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P2D1", "P2D2");
/**
* 3rd partition.
@@ -1203,22 +1229,22 @@
sReplier.addResponse(response3);
// Trigger partition.
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P3D1"); // changed
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P3D2"); // changed
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P3D1", "P3D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P3D1"); // changed
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P3D2"); // changed
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P3D1", "P3D2");
/**
* 4th partition.
@@ -1251,26 +1277,26 @@
sReplier.addResponse(response4);
// Trigger partition.
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("P4D2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("P4D2");
/*
* Finally, autofill and check results.
@@ -1300,8 +1326,8 @@
chosenOne = "P4D2";
}
- mActivity.focusCell(4, 1);
- sUiBot.selectDataset(chosenOne);
+ focusCell(4, 1);
+ mUiBot.selectDataset(chosenOne);
expectation.assertAutoFilled();
}
@@ -1310,10 +1336,6 @@
// Set service.
enableService();
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
/**
* 1st partition.
*/
@@ -1322,19 +1344,18 @@
new CannedDataset.Builder()
.setField(ID_L1C1, "l1c1")
.setField(ID_L1C2, "l1c2")
- .setPresentation(bogusPresentation)
.build());
final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
final CannedFillResponse response1 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth11)
- .setField(ID_L1C1, bogusValue)
- .setField(ID_L1C2, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D1"))
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth12)
- .setField(ID_L1C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D2"))
.build())
.build();
@@ -1344,16 +1365,16 @@
.onCell(1, 2, "l1c2");
// Trigger partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Focus around different fields in the partition.
- sUiBot.assertDatasets("P1D1", "P1D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
+ mUiBot.assertDatasets("P1D1", "P1D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
// Autofill it...
- sUiBot.selectDataset("P1D1");
+ mUiBot.selectDataset("P1D1");
// ... and assert result
expectation1.assertAutoFilled();
@@ -1365,19 +1386,18 @@
final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
new CannedDataset.Builder()
.setField(ID_L2C2, "L2C2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response2 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth21)
.setPresentation(createPresentation("P2D1"))
- .setField(ID_L2C1, bogusValue)
- .setField(ID_L2C2, bogusValue)
+ .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth22)
.setPresentation(createPresentation("P2D2"))
- .setField(ID_L2C2, bogusValue)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response2);
@@ -1385,16 +1405,16 @@
.onCell(2, 2, "L2C2");
// Trigger partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Focus around different fields in the partition.
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P2D1", "P2D2");
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P2D1", "P2D2");
// Autofill it...
- sUiBot.selectDataset("P2D2");
+ mUiBot.selectDataset("P2D2");
// ... and assert result
expectation2.assertAutoFilled();
@@ -1406,21 +1426,20 @@
new CannedDataset.Builder()
.setField(ID_L3C1, "l3c1")
.setField(ID_L3C2, "l3c2")
- .setPresentation(bogusPresentation)
.build());
final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
final CannedFillResponse response3 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth31)
.setPresentation(createPresentation("P3D1"))
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth32)
.setPresentation(createPresentation("P3D2"))
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response3);
@@ -1429,16 +1448,16 @@
.onCell(3, 2, "l3c2");
// Trigger partition.
- mActivity.focusCell(3, 2);
+ focusCell(3, 2);
sReplier.getNextFillRequest();
// Focus around different fields in the partition.
- sUiBot.assertDatasets("P3D1", "P3D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P3D1", "P3D2");
+ mUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P3D1", "P3D2");
// Autofill it...
- sUiBot.selectDataset("P3D1");
+ mUiBot.selectDataset("P3D1");
// ... and assert result
expectation3.assertAutoFilled();
@@ -1451,19 +1470,18 @@
new CannedDataset.Builder()
.setField(ID_L4C1, "L4C1")
.setField(ID_L4C2, "L4C2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response4 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth41)
.setPresentation(createPresentation("P4D1"))
- .setField(ID_L4C1, bogusValue)
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth42)
.setPresentation(createPresentation("P4D2"))
- .setField(ID_L4C1, bogusValue)
- .setField(ID_L4C2, bogusValue)
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response4);
@@ -1472,16 +1490,16 @@
.onCell(4, 2, "L4C2");
// Trigger partition.
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
// Focus around different fields in the partition.
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("P4D2");
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("P4D2");
// Autofill it...
- sUiBot.selectDataset("P4D2");
+ mUiBot.selectDataset("P4D2");
// ... and assert result
expectation4.assertAutoFilled();
}
@@ -1496,10 +1514,6 @@
// Set service.
enableService();
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
/**
* 1st partition.
*/
@@ -1508,19 +1522,18 @@
new CannedDataset.Builder()
.setField(ID_L1C1, "l1c1")
.setField(ID_L1C2, "l1c2")
- .setPresentation(bogusPresentation)
.build());
final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
final CannedFillResponse response1 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth11)
- .setField(ID_L1C1, bogusValue)
- .setField(ID_L1C2, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D1"))
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth12)
- .setField(ID_L1C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D2"))
.build())
.build();
@@ -1530,7 +1543,7 @@
.onCell(1, 2, "l1c2");
// Trigger partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
/**
@@ -1541,19 +1554,18 @@
final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
new CannedDataset.Builder()
.setField(ID_L2C2, "L2C2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response2 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth21)
.setPresentation(createPresentation("P2D1"))
- .setField(ID_L2C1, bogusValue)
- .setField(ID_L2C2, bogusValue)
+ .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth22)
.setPresentation(createPresentation("P2D2"))
- .setField(ID_L2C2, bogusValue)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response2);
@@ -1561,7 +1573,7 @@
.onCell(2, 2, "L2C2");
// Trigger partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
/**
@@ -1572,21 +1584,20 @@
new CannedDataset.Builder()
.setField(ID_L3C1, "l3c1")
.setField(ID_L3C2, "l3c2")
- .setPresentation(bogusPresentation)
.build());
final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
final CannedFillResponse response3 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth31)
.setPresentation(createPresentation("P3D1"))
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth32)
.setPresentation(createPresentation("P3D2"))
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response3);
@@ -1595,7 +1606,7 @@
.onCell(3, 2, "l3c2");
// Trigger partition.
- mActivity.focusCell(3, 2);
+ focusCell(3, 2);
sReplier.getNextFillRequest();
/**
@@ -1607,19 +1618,18 @@
new CannedDataset.Builder()
.setField(ID_L4C1, "L4C1")
.setField(ID_L4C2, "L4C2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response4 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth41)
.setPresentation(createPresentation("P4D1"))
- .setField(ID_L4C1, bogusValue)
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth42)
.setPresentation(createPresentation("P4D2"))
- .setField(ID_L4C1, bogusValue)
- .setField(ID_L4C2, bogusValue)
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response4);
@@ -1627,49 +1637,49 @@
.onCell(4, 1, "L4C1")
.onCell(4, 2, "L4C2");
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
/*
* Now move focus around to make sure the proper values are displayed each time.
*/
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P1D1", "P1D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P1D1", "P1D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P2D1", "P2D2");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P2D1", "P2D2");
- mActivity.focusCell(4, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("P4D2");
+ focusCell(4, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("P4D2");
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P3D1", "P3D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P3D1", "P3D2");
/*
* Finally, autofill and check results.
*/
- mActivity.focusCell(4, 1);
- sUiBot.selectDataset("P4D2");
+ focusCell(4, 1);
+ mUiBot.selectDataset("P4D2");
expectation4.assertAutoFilled();
- mActivity.focusCell(1, 1);
- sUiBot.selectDataset("P1D1");
+ focusCell(1, 1);
+ mUiBot.selectDataset("P1D1");
expectation1.assertAutoFilled();
- mActivity.focusCell(3, 1);
- sUiBot.selectDataset("P3D1");
+ focusCell(3, 1);
+ mUiBot.selectDataset("P3D1");
expectation3.assertAutoFilled();
- mActivity.focusCell(2, 2);
- sUiBot.selectDataset("P2D2");
+ focusCell(2, 2);
+ mUiBot.selectDataset("P2D2");
expectation2.assertAutoFilled();
}
@@ -1683,10 +1693,6 @@
// Set service.
enableService();
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
/**
* 1st partition.
*/
@@ -1700,7 +1706,7 @@
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth12)
- .setField(ID_L1C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D2"))
.build())
.build();
@@ -1710,7 +1716,7 @@
.onCell(1, 2, "l1c2");
// Trigger partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
/**
@@ -1720,7 +1726,6 @@
final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
new CannedDataset.Builder()
.setField(ID_L2C2, "L2C2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response2 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
@@ -1731,7 +1736,7 @@
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth22)
.setPresentation(createPresentation("P2D2"))
- .setField(ID_L2C2, bogusValue)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response2);
@@ -1739,7 +1744,7 @@
.onCell(2, 2, "L2C2");
// Trigger partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
/**
@@ -1750,14 +1755,13 @@
new CannedDataset.Builder()
.setField(ID_L3C1, "l3c1")
.setField(ID_L3C2, "l3c2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response3 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth31)
.setPresentation(createPresentation("P3D1"))
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setPresentation(createPresentation("P3D2"))
@@ -1771,7 +1775,7 @@
.onCell(3, 2, "l3c2");
// Trigger partition.
- mActivity.focusCell(3, 2);
+ focusCell(3, 2);
sReplier.getNextFillRequest();
/**
@@ -1783,7 +1787,7 @@
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth41)
.setPresentation(createPresentation("P4D1"))
- .setField(ID_L4C1, bogusValue)
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setPresentation(createPresentation("P4D2"))
@@ -1796,49 +1800,49 @@
.onCell(4, 1, "L4C1")
.onCell(4, 2, "L4C2");
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
/*
* Now move focus around to make sure the proper values are displayed each time.
*/
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P1D1", "P1D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P1D1", "P1D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P2D1", "P2D2");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P2D1", "P2D2");
- mActivity.focusCell(4, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("P4D2");
+ focusCell(4, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("P4D2");
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P3D1", "P3D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P3D1", "P3D2");
/*
* Finally, autofill and check results.
*/
- mActivity.focusCell(4, 1);
- sUiBot.selectDataset("P4D2");
+ focusCell(4, 1);
+ mUiBot.selectDataset("P4D2");
expectation4.assertAutoFilled();
- mActivity.focusCell(1, 1);
- sUiBot.selectDataset("P1D1");
+ focusCell(1, 1);
+ mUiBot.selectDataset("P1D1");
expectation1.assertAutoFilled();
- mActivity.focusCell(3, 1);
- sUiBot.selectDataset("P3D1");
+ focusCell(3, 1);
+ mUiBot.selectDataset("P3D1");
expectation3.assertAutoFilled();
- mActivity.focusCell(2, 2);
- sUiBot.selectDataset("P2D2");
+ focusCell(2, 2);
+ mUiBot.selectDataset("P2D2");
expectation2.assertAutoFilled();
}
@@ -1876,10 +1880,6 @@
// Set service.
enableService();
- // TODO: current API requires these fields...
- final RemoteViews bogusPresentation = createPresentation("Whatever man, I'm not used...");
- final String bogusValue = "Y U REQUIRE IT?";
-
/**
* 1st partition.
*/
@@ -1893,20 +1893,19 @@
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth12)
- .setField(ID_L1C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D2"))
.build())
.build();
sReplier.addResponse(response1);
// Trigger partition.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P1D1", "P1D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
+ mUiBot.assertDatasets("P1D1", "P1D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
/**
* 2nd partition.
@@ -1917,15 +1916,14 @@
.setField(ID_L1C1, "2l1c1") // from previous partition
.setField(ID_L2C1, "2l2c1")
.setField(ID_L2C2, "2l2c2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response2 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth21)
.setPresentation(createPresentation("P2D1"))
- .setField(ID_L1C1, bogusValue) // from previous partition
- .setField(ID_L2C1, bogusValue)
- .setField(ID_L2C2, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setPresentation(createPresentation("P2D2"))
@@ -1935,18 +1933,18 @@
sReplier.addResponse(response2);
// Trigger partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P2D1"); // changed
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P1D1");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P2D1", "P2D2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P2D1"); // changed
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P1D1");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P2D1", "P2D2");
/**
* 3rd partition.
@@ -1957,44 +1955,43 @@
.setField(ID_L1C2, "3l1c2") // from previous partition
.setField(ID_L3C1, "3l3c1")
.setField(ID_L3C2, "3l3c2")
- .setPresentation(bogusPresentation)
.build());
final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
final CannedFillResponse response3 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth31)
.setPresentation(createPresentation("P3D1"))
- .setField(ID_L1C2, bogusValue) // from previous partition
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth32)
.setPresentation(createPresentation("P3D2"))
- .setField(ID_L2C2, bogusValue) // from previous partition
- .setField(ID_L3C1, bogusValue)
- .setField(ID_L3C2, bogusValue)
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response3);
// Trigger partition.
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P3D1"); // changed
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P2D1");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P3D2"); // changed
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P3D1", "P3D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P3D1"); // changed
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P2D1");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P3D2"); // changed
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P3D1", "P3D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P3D1", "P3D2");
/**
* 4th partition.
@@ -2009,7 +2006,6 @@
.setField(ID_L3C1, "4l3c1") // from previous partition
.setField(ID_L3C2, "4l3c2") // from previous partition
.setField(ID_L4C1, "4l4c1")
- .setPresentation(bogusPresentation)
.build());
final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
new CannedDataset.Builder()
@@ -2022,57 +2018,56 @@
.setField(ID_L1C1, "4L1C1") // from previous partition
.setField(ID_L4C1, "4L4C1")
.setField(ID_L4C2, "4L4C2")
- .setPresentation(bogusPresentation)
.build());
final CannedFillResponse response4 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth41)
.setPresentation(createPresentation("P4D1"))
- .setField(ID_L1C1, bogusValue) // from previous partition
- .setField(ID_L1C2, bogusValue) // from previous partition
- .setField(ID_L2C1, bogusValue) // from previous partition
- .setField(ID_L2C2, bogusValue) // from previous partition
- .setField(ID_L3C1, bogusValue) // from previous partition
- .setField(ID_L3C2, bogusValue) // from previous partition
- .setField(ID_L4C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth42)
.setPresentation(createPresentation("P4D2"))
- .setField(ID_L1C1, bogusValue) // from previous partition
- .setField(ID_L1C2, bogusValue) // from previous partition
- .setField(ID_L2C1, bogusValue) // from previous partition
- .setField(ID_L2C2, bogusValue) // from previous partition
- .setField(ID_L3C1, bogusValue) // from previous partition
- .setField(ID_L3C2, bogusValue) // from previous partition
- .setField(ID_L1C1, bogusValue) // from previous partition
- .setField(ID_L4C1, bogusValue)
- .setField(ID_L4C2, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+ .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+ .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
.build())
.build();
sReplier.addResponse(response4);
// Trigger partition.
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
// Asserts proper datasets are shown on each field defined so far.
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 1);
- sUiBot.assertDatasets("P4D1", "P4D2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("P4D2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(3, 2);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 1);
+ mUiBot.assertDatasets("P4D1", "P4D2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("P4D2");
/*
* Finally, autofill and check results.
@@ -2102,9 +2097,9 @@
chosenOne = "P4D2";
}
- mActivity.focusCell(4, 1);
- sUiBot.selectDataset(chosenOne);
- expectation.assertAutoFilled();
+ focusCell(4, 1);
+ mUiBot.selectDataset(chosenOne);
+ expectation.assertAutoFilled();
}
@Test
@@ -2129,10 +2124,10 @@
final FillExpectation expectation1 = mActivity.expectAutofill()
.onCell(1, 1, "l1c1")
.onCell(1, 2, "l1c2");
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("Auth 1");
+ mUiBot.assertDatasets("Auth 1");
// Prepare 2nd partition.
final IntentSender auth2 = AuthenticationActivity.createSender(getContext(), 2,
@@ -2151,10 +2146,10 @@
final FillExpectation expectation2 = mActivity.expectAutofill()
.onCell(2, 1, "l2c1")
.onCell(2, 2, "l2c2");
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("Auth 2");
+ mUiBot.assertDatasets("Auth 2");
// Prepare 3rd partition.
final IntentSender auth3 = AuthenticationActivity.createSender(getContext(), 3,
@@ -2173,10 +2168,10 @@
final FillExpectation expectation3 = mActivity.expectAutofill()
.onCell(3, 1, "l3c1")
.onCell(3, 2, "l3c2");
- mActivity.focusCell(3, 1);
+ focusCell(3, 1);
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("Auth 3");
+ mUiBot.assertDatasets("Auth 3");
// Prepare 4th partition.
final IntentSender auth4 = AuthenticationActivity.createSender(getContext(), 4,
@@ -2195,52 +2190,52 @@
final FillExpectation expectation4 = mActivity.expectAutofill()
.onCell(4, 1, "l4c1")
.onCell(4, 2, "l4c2");
- mActivity.focusCell(4, 1);
+ focusCell(4, 1);
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("Auth 4");
+ mUiBot.assertDatasets("Auth 4");
// Now play around the focus to make sure they still display the right values.
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("Auth 1");
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("Auth 1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("Auth 1");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("Auth 1");
- mActivity.focusCell(3, 1);
- sUiBot.assertDatasets("Auth 3");
- mActivity.focusCell(3, 2);
- sUiBot.assertDatasets("Auth 3");
+ focusCell(3, 1);
+ mUiBot.assertDatasets("Auth 3");
+ focusCell(3, 2);
+ mUiBot.assertDatasets("Auth 3");
- mActivity.focusCell(2, 1);
- sUiBot.assertDatasets("Auth 2");
- mActivity.focusCell(4, 2);
- sUiBot.assertDatasets("Auth 4");
+ focusCell(2, 1);
+ mUiBot.assertDatasets("Auth 2");
+ focusCell(4, 2);
+ mUiBot.assertDatasets("Auth 4");
- mActivity.focusCell(2, 2);
- sUiBot.assertDatasets("Auth 2");
- mActivity.focusCell(4, 1);
- sUiBot.assertDatasets("Auth 4");
+ focusCell(2, 2);
+ mUiBot.assertDatasets("Auth 2");
+ focusCell(4, 1);
+ mUiBot.assertDatasets("Auth 4");
// Finally, autofill and check them.
- mActivity.focusCell(2, 1);
- sUiBot.selectDataset("Auth 2");
- sUiBot.selectDataset("Partition 2");
+ focusCell(2, 1);
+ mUiBot.selectDataset("Auth 2");
+ mUiBot.selectDataset("Partition 2");
expectation2.assertAutoFilled();
- mActivity.focusCell(4, 1);
- sUiBot.selectDataset("Auth 4");
- sUiBot.selectDataset("Partition 4");
+ focusCell(4, 1);
+ mUiBot.selectDataset("Auth 4");
+ mUiBot.selectDataset("Partition 4");
expectation4.assertAutoFilled();
- mActivity.focusCell(3, 1);
- sUiBot.selectDataset("Auth 3");
- sUiBot.selectDataset("Partition 3");
+ focusCell(3, 1);
+ mUiBot.selectDataset("Auth 3");
+ mUiBot.selectDataset("Partition 3");
expectation3.assertAutoFilled();
- mActivity.focusCell(1, 1);
- sUiBot.selectDataset("Auth 1");
- sUiBot.selectDataset("Partition 1");
+ focusCell(1, 1);
+ mUiBot.selectDataset("Auth 1");
+ mUiBot.selectDataset("Partition 1");
expectation1.assertAutoFilled();
}
@@ -2262,13 +2257,13 @@
sReplier.addResponse(response1);
// Trigger autofill.
- mActivity.focusCell(1, 1);
+ focusCell(1, 1);
sReplier.getNextFillRequest();
// Make sure UI is shown, but don't tap it.
- sUiBot.assertDatasets("l1c1");
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("l1c2");
+ mUiBot.assertDatasets("l1c1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("l1c2");
// Prepare 2nd partition.
final CannedFillResponse response2 = new CannedFillResponse.Builder()
@@ -2279,16 +2274,16 @@
sReplier.addResponse(response2);
// Trigger autofill on 2nd partition.
- mActivity.focusCell(2, 1);
+ focusCell(2, 1);
// Make sure it was ignored.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Make sure 1st partition is still working.
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("l1c2");
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("l1c1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("l1c2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("l1c1");
// Prepare 3rd partition.
final CannedFillResponse response3 = new CannedFillResponse.Builder()
@@ -2298,21 +2293,21 @@
.build();
sReplier.addResponse(response3);
// Trigger autofill on 3rd partition.
- mActivity.focusCell(3, 2);
+ focusCell(3, 2);
// Make sure it was ignored.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Make sure 1st partition is still working...
- mActivity.focusCell(1, 2);
- sUiBot.assertDatasets("l1c2");
- mActivity.focusCell(1, 1);
- sUiBot.assertDatasets("l1c1");
+ focusCell(1, 2);
+ mUiBot.assertDatasets("l1c2");
+ focusCell(1, 1);
+ mUiBot.assertDatasets("l1c1");
//...and can be autofilled.
final FillExpectation expectation = mActivity.expectAutofill()
.onCell(1, 1, "l1c1");
- sUiBot.selectDataset("l1c1");
+ mUiBot.selectDataset("l1c1");
expectation.assertAutoFilled();
} finally {
setMaxPartitions(maxBefore);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
index 75c742f..0f571ef 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
@@ -20,6 +20,7 @@
import static android.autofillservice.cts.Helper.ID_USERNAME;
import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.assertTextFromResouces;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertTextOnly;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
@@ -28,7 +29,6 @@
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -49,11 +49,6 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
@Test
public void testSanitization() throws Exception {
// Set service.
@@ -78,8 +73,8 @@
assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
// ...password label should be ok because it was set from other resource id
- assertTextOnly(findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL),
- "DA PASSWORD");
+ assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+ "new_password_label");
// ...username and password should be ok because they were set in the SML
assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
@@ -92,13 +87,13 @@
mActivity.tapLogin();
// Assert the snack bar is shown and tap "Save".
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
// Assert sanitization on save: everything should be available!
assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
- assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_PASSWORD_LABEL),
- "DA PASSWORD");
+ assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+ "new_password_label");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
index 5cdcf9b..1cb5942 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
@@ -29,10 +29,20 @@
static final String ID_PRE_LABEL = "preLabel";
static final String ID_PRE_INPUT = "preInput";
+ private static PreSimpleSaveActivity sInstance;
+
TextView mPreLabel;
EditText mPreInput;
Button mSubmit;
+ public static PreSimpleSaveActivity getInstance() {
+ return sInstance;
+ }
+
+ public PreSimpleSaveActivity() {
+ sInstance = this;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -48,4 +58,22 @@
startActivity(new Intent(this, SimpleSaveActivity.class));
});
}
+
+ FillExpectation expectAutoFill(String input) {
+ final FillExpectation expectation = new FillExpectation(input);
+ mPreInput.addTextChangedListener(expectation.mInputWatcher);
+ return expectation;
+ }
+
+ final class FillExpectation {
+ private final OneTimeTextWatcher mInputWatcher;
+
+ private FillExpectation(String input) {
+ mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
+ }
+
+ void assertAutoFilled() throws Exception {
+ mInputWatcher.assertAutoFilled();
+ }
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
index b193ddf..504f8ed 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
@@ -15,6 +15,7 @@
*/
package android.autofillservice.cts;
+import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
import static android.autofillservice.cts.Helper.assertTextAndValue;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
@@ -24,13 +25,23 @@
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static com.google.common.truth.Truth.assertThat;
+
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.content.Intent;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.view.View;
+import android.widget.RemoteViews;
import org.junit.Rule;
+import java.util.regex.Pattern;
+
public class PreSimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase {
@Rule
@@ -72,27 +83,28 @@
mActivity.mSubmit.performClick();
});
// Make sure post-save activity is shown...
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// Tap the link.
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
tapSaveUiLink(saveUi);
// Make sure new activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
// .. then do something to return to previous activity...
switch (type) {
case ROTATE_THEN_TAP_BACK_BUTTON:
- sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+ mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
// not breaking on purpose
case TAP_BACK_BUTTON:
- sUiBot.pressBack();
+ mUiBot.pressBack();
break;
case FINISH_ACTIVITY:
// ..then finishes it.
- WelcomeActivity.finishIt();
+ WelcomeActivity.finishIt(mUiBot);
break;
default:
throw new IllegalArgumentException("invalid type: " + type);
@@ -100,7 +112,7 @@
// ... and tap save.
final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
- sUiBot.saveForAutofill(newSaveUi, true);
+ mUiBot.saveForAutofill(newSaveUi, true);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT), "108");
@@ -130,32 +142,32 @@
mActivity.mSubmit.performClick();
});
// Make sure post-save activity is shown...
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// Tap the link.
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
tapSaveUiLink(saveUi);
// Make sure new activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
// Tap back to restore the Save UI...
- sUiBot.pressBack();
+ mUiBot.pressBack();
// ...but don't tap it...
- final UiObject2 saveUi2 = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+ final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
// ...instead, do something to dismiss it:
switch (action) {
case TOUCH_OUTSIDE:
- sUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+ mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
break;
case TAP_NO_ON_SAVE_UI:
- sUiBot.saveForAutofill(saveUi2, false);
+ mUiBot.saveForAutofill(saveUi2, false);
break;
case TAP_YES_ON_SAVE_UI:
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT),
@@ -165,7 +177,7 @@
default:
throw new IllegalArgumentException("invalid action: " + action);
}
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
// Make sure previous session was finished.
Helper.assertNoDanglingSessions();
@@ -192,10 +204,10 @@
newActivty.mCommit.performClick();
});
// Make sure post-save activity is shown...
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// Save it...
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
// ... and assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -226,39 +238,39 @@
mActivity.mSubmit.performClick();
});
// Make sure post-save activity is shown...
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// Tap the link.
final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
tapSaveUiLink(saveUi);
// Make sure linked activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
switch (type) {
case TAP_RECENTS:
- sUiBot.switchAppsUsingRecents();
+ mUiBot.switchAppsUsingRecents();
// Make sure right activity is showing.
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
break;
case LAUNCH_PREVIOUS_ACTIVITY:
startActivity(PreSimpleSaveActivity.class);
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
break;
case LAUNCH_NEW_ACTIVITY:
// Launch a 3rd activity...
startActivity(LoginActivity.class);
- sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+ mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
// ...then go back
- sUiBot.pressBack();
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.pressBack();
+ mUiBot.assertShownByRelativeId(ID_INPUT);
break;
default:
throw new IllegalArgumentException("invalid type: " + type);
}
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Override
@@ -294,14 +306,14 @@
tapSaveUiLink(saveUi);
// Make sure new activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
// Save UI should be showing as well, since Trampoline finished.
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
// Go back and make sure it's showing the right activity.
- sUiBot.pressBack();
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.pressBack();
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// Now triggers a new session in the new activity (SaveActivity) and do business as usual...
sReplier.addResponse(new CannedFillResponse.Builder()
@@ -321,13 +333,67 @@
newActivty.mCommit.performClick();
});
// Make sure post-save activity is shown...
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// Save it...
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
// ... and assert results
final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_INPUT), "42");
}
+
+ @Override
+ protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+ startActivity(false);
+ // Set service.
+ enableService();
+
+ final CustomDescription.Builder customDescription =
+ newCustomDescriptionBuilder(WelcomeActivity.class);
+ final RemoteViews update = newTemplate();
+ if (updateLinkView) {
+ update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+ } else {
+ update.setCharSequence(R.id.static_text, "setText", "ME!");
+ }
+ Validator validCondition = new RegexValidator(mActivity.mPreInput.getAutofillId(),
+ Pattern.compile(".*"));
+ customDescription.batchUpdate(validCondition,
+ new BatchUpdates.Builder().updateTemplate(update).build());
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setCustomDescription(customDescription.build())
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+ sReplier.getNextFillRequest();
+ Helper.assertHasSessions(mPackageName);
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mPreInput.setText("108");
+ mActivity.mSubmit.performClick();
+ });
+ // Make sure post-save activity is shown...
+ mUiBot.assertShownByRelativeId(ID_INPUT);
+
+ // Tap the link.
+ final UiObject2 saveUi;
+ if (updateLinkView) {
+ saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD, "TAP ME IF YOU CAN");
+ } else {
+ saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+ final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+ assertThat(changed.getText()).isEqualTo("ME!");
+ }
+ tapSaveUiLink(saveUi);
+
+ // Make sure new activity is shown...
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
index be740b1..2de94f8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
@@ -45,17 +45,24 @@
@Override
public void evaluate() throws Throwable {
+ final String name = description.getDisplayName();
Throwable caught = null;
for (int i = 1; i <= mMaxAttempts; i++) {
try {
base.evaluate();
return;
- } catch (RetryableException | StaleObjectException e) {
+ } catch (RetryableException e) {
+ final Timeout timeout = e.getTimeout();
+ if (timeout != null) {
+ timeout.increase();
+ }
caught = e;
- Log.w(TAG,
- description.getDisplayName() + ": attempt " + i + " failed: " + e);
+ } catch (StaleObjectException e) {
+ caught = e;
}
+ Log.w(TAG, name + ": attempt " + i + " failed: " + caught);
}
+ Log.e(TAG, name + ": giving up after " + mMaxAttempts + " attempts");
throw caught;
}
};
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
index 3b733f6..473ec4e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRuleTest.java
@@ -68,6 +68,17 @@
}
@Test
+ public void testPassOnRetryableExceptionWithTimeout() throws Throwable {
+ final Timeout timeout = new Timeout("YOUR TIME IS GONE", 1, 2, 10);
+ final RetryableException exception = new RetryableException(timeout, "Y U NO?");
+ final RetryRule rule = new RetryRule(2);
+ rule.apply(new RetryableStatement<RetryableException>(1, exception), mDescription)
+ .evaluate();
+ // Assert timeout was increased
+ assertThat(timeout.ms()).isEqualTo(2);
+ }
+
+ @Test
public void testFailOnRetryableException() throws Throwable {
final RetryRule rule = new RetryRule(2);
try {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java b/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java
index 7ca7d62..55b4d5c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryableException.java
@@ -16,20 +16,52 @@
package android.autofillservice.cts;
+import android.support.annotation.Nullable;
+
/**
* Exception that cause the {@link RetryRule} to re-try a test.
*/
public class RetryableException extends RuntimeException {
+ @Nullable
+ private final Timeout mTimeout;
+
public RetryableException(String msg) {
- super(msg);
+ this((Timeout) null, msg);
}
public RetryableException(String format, Object...args) {
- super(String.format(format, args));
+ this((Timeout) null, String.format(format, args));
}
public RetryableException(Throwable cause, String format, Object...args) {
+ this((Timeout) null, String.format(format, args), cause);
+ }
+
+ public RetryableException(@Nullable Timeout timeout, String msg) {
+ super(msg);
+ this.mTimeout = timeout;
+ }
+
+ public RetryableException(@Nullable Timeout timeout, String format, Object...args) {
+ super(String.format(format, args));
+ this.mTimeout = timeout;
+ }
+
+ public RetryableException(@Nullable Timeout timeout, Throwable cause, String format,
+ Object...args) {
super(String.format(format, args), cause);
+ this.mTimeout = timeout;
+ }
+
+ @Nullable
+ public Timeout getTimeout() {
+ return mTimeout;
+ }
+
+ @Override
+ public String getMessage() {
+ final String superMessage = super.getMessage();
+ return mTimeout == null ? superMessage : superMessage + " (timeout=" + mTimeout + ")";
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java
new file mode 100644
index 0000000..bceb11b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Rule used to safely run clean up code after a test is finished, so that exceptions thrown by
+ * the cleanup code don't hide exception thrown by the test body
+ */
+// TODO: move to common CTS code
+public final class SafeCleanerRule implements TestRule {
+
+ private static final String TAG = "SafeCleanerRule";
+
+ private final List<Runnable> mCleaners = new ArrayList<>();
+ private final List<Callable<List<Throwable>>> mExtraThrowables = new ArrayList<>();
+ private final List<Throwable> mThrowables = new ArrayList<>();
+
+ /**
+ * Runs {@code cleaner} after the test is finished, catching any {@link Throwable} thrown by it.
+ */
+ public SafeCleanerRule run(@NonNull Runnable cleaner) {
+ mCleaners.add(cleaner);
+ return this;
+ }
+
+ /**
+ * Adds exceptions directly.
+ *
+ * <p>Typically used when exceptions were caught asychronously during the test execution.
+ */
+ public SafeCleanerRule add(@NonNull Callable<List<Throwable>> exceptions) {
+ mExtraThrowables.add(exceptions);
+ return this;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ // First run the test
+ try {
+ base.evaluate();
+ } catch (Throwable t) {
+ Log.w(TAG, "Adding exception from main test");
+ mThrowables.add(t);
+ }
+
+ // Then the cleanup runners
+ for (Runnable runner : mCleaners) {
+ try {
+ runner.run();
+ } catch (Throwable t) {
+ Log.w(TAG, "Adding exception from cleaner");
+ mThrowables.add(t);
+ }
+ }
+
+ // And finally add the extra exceptions
+ for (Callable<List<Throwable>> extraThrowablesCallable : mExtraThrowables) {
+ final List<Throwable> extraThrowables = extraThrowablesCallable.call();
+ if (extraThrowables != null) {
+ Log.w(TAG, "Adding " + extraThrowables.size() + " extra exceptions");
+ mThrowables.addAll(extraThrowables);
+ }
+ }
+
+ // Finally, throw up!
+ if (mThrowables.isEmpty()) return;
+
+ final int numberExceptions = mThrowables.size();
+ if (numberExceptions == 1) {
+ throw mThrowables.get(0);
+ }
+ throw new MultipleExceptions(mThrowables);
+ }
+ };
+ }
+
+ private static String toMesssage(List<Throwable> throwables) {
+ String msg = "D'OH!";
+ try {
+ try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+ sw.write("Caught " + throwables.size() + " exceptions\n");
+ for (int i = 0; i < throwables.size(); i++) {
+ sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
+ final Throwable exception = throwables.get(i);
+ exception.printStackTrace(pw);
+ sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
+ }
+ msg = sw.toString();
+ }
+ } catch (IOException e) {
+ // ignore close() errors - should not happen...
+ Log.e(TAG, "Exception closing StringWriter: " + e);
+ }
+ return msg;
+ }
+
+ // VisibleForTesting
+ static class MultipleExceptions extends AssertionError {
+ private final List<Throwable> mThrowables;
+
+ private MultipleExceptions(List<Throwable> throwables) {
+ super(toMesssage(throwables));
+
+ this.mThrowables = throwables;
+ }
+
+ List<Throwable> getThrowables() {
+ return mThrowables;
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java
new file mode 100644
index 0000000..7fc021e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.expectThrows;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SafeCleanerRuleTest {
+
+ private static class FailureStatement extends Statement {
+ private final Throwable mThrowable;
+
+ FailureStatement(Throwable t) {
+ mThrowable = t;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ throw mThrowable;
+ }
+ }
+
+ private final Description mDescription = Description.createSuiteDescription("Whatever");
+ private final RuntimeException mRuntimeException = new RuntimeException("D'OH!");
+
+ // Use mocks for objects that don't throw any exception.
+ @Mock private Runnable mGoodGuyRunner1;
+ @Mock private Runnable mGoodGuyRunner2;
+ @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions1;
+ @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions2;
+ @Mock private Statement mGoodGuyStatement;
+
+ @Test
+ public void testEmptyRule_testPass() throws Throwable {
+ final SafeCleanerRule rule = new SafeCleanerRule();
+ rule.apply(mGoodGuyStatement, mDescription).evaluate();
+ }
+
+ @Test
+ public void testEmptyRule_testFails() throws Throwable {
+ final SafeCleanerRule rule = new SafeCleanerRule();
+ final Throwable actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+ }
+
+ @Test
+ public void testOnlyTestFails() throws Throwable {
+ final SafeCleanerRule rule = new SafeCleanerRule()
+ .run(mGoodGuyRunner1)
+ .add(mGoodGuyExtraExceptions1);
+ final Throwable actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+ verify(mGoodGuyRunner1).run();
+ verify(mGoodGuyExtraExceptions1).call();
+ }
+
+ @Test
+ public void testTestPass_oneRunnerFails() throws Throwable {
+ final SafeCleanerRule rule = new SafeCleanerRule()
+ .run(mGoodGuyRunner1)
+ .run(() -> { throw mRuntimeException; })
+ .run(mGoodGuyRunner2)
+ .add(mGoodGuyExtraExceptions1);
+ final Throwable actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+ verify(mGoodGuyRunner1).run();
+ verify(mGoodGuyRunner2).run();
+ verify(mGoodGuyExtraExceptions1).call();
+ }
+
+ @Test
+ public void testTestPass_oneExtraExceptionThrown() throws Throwable {
+ final SafeCleanerRule rule = new SafeCleanerRule()
+ .run(mGoodGuyRunner1)
+ .add(() -> { return ImmutableList.of(mRuntimeException); })
+ .add(mGoodGuyExtraExceptions1)
+ .run(mGoodGuyRunner2);
+ final Throwable actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+ verify(mGoodGuyRunner1).run();
+ verify(mGoodGuyRunner2).run();
+ verify(mGoodGuyExtraExceptions1).call();
+ }
+
+ @Test
+ public void testThrowTheKitchenSinkAKAEverybodyThrows() throws Throwable {
+ final Exception extra1 = new Exception("1");
+ final Exception extra2 = new Exception("2");
+ final Exception extra3 = new Exception("3");
+ final Error error1 = new Error("one");
+ final Error error2 = new Error("two");
+ final RuntimeException testException = new RuntimeException("TEST, Y U NO PASS?");
+ final SafeCleanerRule rule = new SafeCleanerRule()
+ .run(mGoodGuyRunner1)
+ .add(mGoodGuyExtraExceptions1)
+ .add(() -> { return ImmutableList.of(extra1, extra2); })
+ .run(() -> {
+ throw error1;
+ })
+ .run(mGoodGuyRunner2)
+ .add(() -> { return ImmutableList.of(extra3); })
+ .add(mGoodGuyExtraExceptions2)
+ .run(() -> { throw error2; });
+
+ final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
+ SafeCleanerRule.MultipleExceptions.class,
+ () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
+ assertThat(actualException.getThrowables())
+ .containsExactly(testException, error1, error2, extra1, extra2, extra3)
+ .inOrder();
+ verify(mGoodGuyRunner1).run();
+ verify(mGoodGuyRunner2).run();
+ verify(mGoodGuyExtraExceptions1).call();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
index 702e1b1..d34eda1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
@@ -16,8 +16,11 @@
package android.autofillservice.cts;
+import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertThrows;
+import android.service.autofill.InternalSanitizer;
+import android.service.autofill.Sanitizer;
import android.service.autofill.SaveInfo;
import android.support.test.runner.AndroidJUnit4;
import android.view.autofill.AutofillId;
@@ -28,6 +31,9 @@
@RunWith(AndroidJUnit4.class)
public class SaveInfoTest {
+ private final AutofillId mId = new AutofillId(42);
+ private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
+
@Test
public void testRequiredIdsBuilder_null() {
assertThrows(IllegalArgumentException.class,
@@ -56,14 +62,14 @@
@Test
public void testSetOptionalIds_null() {
final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
- new AutofillId[] { new AutofillId(42) });
+ new AutofillId[] { mId });
assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
}
@Test
public void testSetOptional_empty() {
final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
- new AutofillId[] { new AutofillId(42) });
+ new AutofillId[] { mId });
assertThrows(IllegalArgumentException.class,
() -> builder.setOptionalIds(new AutofillId[] {}));
}
@@ -71,8 +77,38 @@
@Test
public void testSetOptional_nullEntry() {
final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
- new AutofillId[] { new AutofillId(42) });
+ new AutofillId[] { mId });
assertThrows(IllegalArgumentException.class,
() -> builder.setOptionalIds(new AutofillId[] { null }));
}
+
+ @Test
+ public void testAddSanitizer_illegalArgs() {
+ final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+ new AutofillId[] { mId });
+ // Null sanitizer
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSanitizer(null, mId));
+ // Invalid sanitizer class
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSanitizer(mock(Sanitizer.class), mId));
+ // Null ids
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSanitizer(mSanitizer, (AutofillId[]) null));
+ // Empty ids
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSanitizer(mSanitizer, new AutofillId[] {}));
+ // Repeated ids
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSanitizer(mSanitizer, new AutofillId[] {mId, mId}));
+ }
+
+ @Test
+ public void testAddSanitizer_sameIdOnDifferentCalls() {
+ final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+ new AutofillId[] { mId });
+ builder.addSanitizer(mSanitizer, mId);
+ assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
+ }
+
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index bd26426..0df508b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -19,16 +19,15 @@
import static android.autofillservice.cts.Helper.ID_LOGIN;
import static android.autofillservice.cts.Helper.ID_PASSWORD;
import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.eventually;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.Helper.getContext;
import static android.autofillservice.cts.Helper.getOutOfProcessPid;
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.OutOfProcessLoginActivity.getStartedMarker;
import static android.autofillservice.cts.OutOfProcessLoginActivity.getStoppedMarker;
import static android.autofillservice.cts.UiBot.LANDSCAPE;
import static android.autofillservice.cts.UiBot.PORTRAIT;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +45,8 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.Callable;
+
/**
* Test the lifecycle of a autofill session
*/
@@ -56,6 +57,27 @@
private static final String BUTTON_FULL_ID = "android.autofillservice.cts:id/button";
private static final String CANCEL_FULL_ID = "android.autofillservice.cts:id/cancel";
+ /**
+ * Delay for activity start/stop.
+ * TODO figure out a better way to wait without using sleep().
+ */
+ private static final long WAIT_ACTIVITY_MS = 1000;
+
+ private static final Timeout SESSION_LIFECYCLE_TIMEOUT = new Timeout(
+ "SESSION_LIFECYCLE_TIMEOUT", 500, 2F, 5000);
+
+ /**
+ * Runs an {@code assertion}, retrying until {@code timeout} is reached.
+ */
+ private static void eventually(String description, Callable<Boolean> assertion)
+ throws Exception {
+ SESSION_LIFECYCLE_TIMEOUT.run(description, assertion);
+ }
+
+ public SessionLifecycleTest() {
+ super(new UiBot(SESSION_LIFECYCLE_TIMEOUT));
+ }
+
@Before
public void cleanUpState() {
Helper.preTestCleanup();
@@ -65,8 +87,8 @@
* Prevents the screen to rotate by itself
*/
@Before
- public void disableAutoRotation() {
- Helper.disableAutoRotation(sUiBot);
+ public void disableAutoRotation() throws Exception {
+ Helper.disableAutoRotation(mUiBot);
}
/**
@@ -79,14 +101,30 @@
private void killOfProcessLoginActivityProcess() throws Exception {
// Waiting for activity to stop (stop marker appears)
- eventually(() -> assertThat(getStoppedMarker(getContext()).exists()).isTrue());
+ eventually("getStoppedMarker()", () -> {
+ return getStoppedMarker(getContext()).exists();
+ });
// onStop might not be finished, hence wait more
- SystemClock.sleep(1000);
+ SystemClock.sleep(WAIT_ACTIVITY_MS);
// Kill activity that is in the background
runShellCommand("kill -9 %d",
- getOutOfProcessPid("android.autofillservice.cts.outside"));
+ getOutOfProcessPid("android.autofillservice.cts.outside",
+ SESSION_LIFECYCLE_TIMEOUT));
+ }
+
+ private void startAndWaitExternalActivity() throws Exception {
+ Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
+ OutOfProcessLoginActivity.class);
+ getStartedMarker(getContext()).delete();
+ getContext().startActivity(outOfProcessAcvitityStartIntent);
+ eventually("getStartedMarker()", () -> {
+ return getStartedMarker(getContext()).exists();
+ });
+ getStartedMarker(getContext()).delete();
+ // Even if we wait the activity started, UiObject still fails. Have to wait a little bit.
+ SystemClock.sleep(WAIT_ACTIVITY_MS);
}
@Test
@@ -95,9 +133,7 @@
enableService();
// Start activity that is autofilled in a separate process so it can be killed
- Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
- OutOfProcessLoginActivity.class);
- getContext().startActivity(outOfProcessAcvitityStartIntent);
+ startAndWaitExternalActivity();
// Set expectations.
final Bundle extras = new Bundle();
@@ -123,62 +159,66 @@
sReplier.addResponse(response);
// Trigger autofill on username
- sUiBot.selectById(USERNAME_FULL_ID);
+ mUiBot.selectById(USERNAME_FULL_ID);
// Wait for fill request to be processed
sReplier.getNextFillRequest();
// Wait until authentication is shown
- sUiBot.assertDatasets("authenticate");
+ mUiBot.assertDatasets("authenticate");
// Change orientation which triggers a destroy -> create in the app as the activity
// cannot deal with such situations
- sUiBot.setScreenOrientation(LANDSCAPE);
+ mUiBot.setScreenOrientation(LANDSCAPE);
+
+ // Wait context and Views being recreated in rotation
+ mUiBot.assertShownById(USERNAME_FULL_ID);
// Delete stopped marker
getStoppedMarker(getContext()).delete();
// Authenticate
- sUiBot.selectDataset("authenticate");
+ mUiBot.selectDataset("authenticate");
// Kill activity that is in the background
killOfProcessLoginActivityProcess();
// Change orientation which triggers a destroy -> create in the app as the activity
// cannot deal with such situations
- sUiBot.setScreenOrientation(PORTRAIT);
+ mUiBot.setScreenOrientation(PORTRAIT);
// Approve authentication
- sUiBot.selectById(BUTTON_FULL_ID);
+ mUiBot.selectById(BUTTON_FULL_ID);
// Wait for dataset to be shown
- sUiBot.assertDatasets("dataset");
+ mUiBot.assertDatasets("dataset");
// Change orientation which triggers a destroy -> create in the app as the activity
// cannot deal with such situations
- sUiBot.setScreenOrientation(LANDSCAPE);
+ mUiBot.setScreenOrientation(LANDSCAPE);
// Select dataset
- sUiBot.selectDataset("dataset");
+ mUiBot.selectDataset("dataset");
// Check the results.
- eventually(() -> assertThat(sUiBot.getTextById(USERNAME_FULL_ID)).isEqualTo(
- "autofilled username"));
+ eventually("getTextById(" + USERNAME_FULL_ID + ")", () -> {
+ return mUiBot.getTextById(USERNAME_FULL_ID).equals("autofilled username");
+ });
// Set password
- sUiBot.setTextById(PASSWORD_FULL_ID, "new password");
+ mUiBot.setTextById(PASSWORD_FULL_ID, "new password");
// Login
- sUiBot.selectById(LOGIN_FULL_ID);
+ mUiBot.selectById(LOGIN_FULL_ID);
// Wait for save UI to be shown
- sUiBot.assertShownById("android:id/autofill_save_yes");
+ mUiBot.assertShownById("android:id/autofill_save_yes");
// Change orientation to make sure save UI can handle this
- sUiBot.setScreenOrientation(PORTRAIT);
+ mUiBot.setScreenOrientation(PORTRAIT);
// Tap "Save".
- sUiBot.selectById("android:id/autofill_save_yes");
+ mUiBot.selectById("android:id/autofill_save_yes");
// Get save request
InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -197,7 +237,9 @@
final String extraValue = saveRequest.data.getString("numbers");
assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
- eventually(() -> assertNoDanglingSessions());
+ eventually("assert dangling sessions", () -> {
+ return Helper.listSessions().isEmpty();
+ });
}
@Test
@@ -206,9 +248,7 @@
enableService();
// Start activity that is autofilled in a separate process so it can be killed
- Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
- OutOfProcessLoginActivity.class);
- getContext().startActivity(outOfProcessAcvitityStartIntent);
+ startAndWaitExternalActivity();
// Create the authentication intent (launching a full screen activity)
IntentSender authentication = PendingIntent.getActivity(getContext(), 0,
@@ -222,28 +262,28 @@
sReplier.addResponse(response);
// Trigger autofill on username
- sUiBot.selectById(USERNAME_FULL_ID);
+ mUiBot.selectById(USERNAME_FULL_ID);
// Wait for fill request to be processed
sReplier.getNextFillRequest();
// Wait until authentication is shown
- sUiBot.assertDatasets("authenticate");
+ mUiBot.assertDatasets("authenticate");
// Delete stopped marker
getStoppedMarker(getContext()).delete();
// Authenticate
- sUiBot.selectDataset("authenticate");
+ mUiBot.selectDataset("authenticate");
// Kill activity that is in the background
killOfProcessLoginActivityProcess();
// Cancel authentication activity
- sUiBot.pressBack();
+ mUiBot.pressBack();
// Authentication should still be shown
- sUiBot.assertDatasets("authenticate");
+ mUiBot.assertDatasets("authenticate");
}
@Test
@@ -252,25 +292,23 @@
enableService();
// Start activity that is autofilled in a separate process so it can be killed
- Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
- OutOfProcessLoginActivity.class);
- getContext().startActivity(outOfProcessAcvitityStartIntent);
+ startAndWaitExternalActivity();
CannedFillResponse response = new CannedFillResponse.Builder()
.addDataset(new CannedFillResponse.CannedDataset.Builder(
createPresentation("dataset"))
- .setField(ID_USERNAME, "filled").build())
+ .setField(ID_USERNAME, "filled").build())
.build();
sReplier.addResponse(response);
// Trigger autofill on username
- sUiBot.selectById(USERNAME_FULL_ID);
+ mUiBot.selectById(USERNAME_FULL_ID);
// Wait for fill request to be processed
sReplier.getNextFillRequest();
// Wait until dataset is shown
- sUiBot.assertDatasets("dataset");
+ mUiBot.assertDatasets("dataset");
// Delete stopped marker
getStoppedMarker(getContext()).delete();
@@ -285,10 +323,10 @@
killOfProcessLoginActivityProcess();
// Cancel activity on top
- sUiBot.pressBack();
+ mUiBot.pressBack();
// Dataset should still be shown
- sUiBot.assertDatasets("dataset");
+ mUiBot.assertDatasets("dataset");
}
@Test
@@ -297,26 +335,24 @@
enableService();
// Start activity that is autofilled in a separate process so it can be killed
- Intent outOfProcessAcvitityStartIntent = new Intent(getContext(),
- OutOfProcessLoginActivity.class);
- getContext().startActivity(outOfProcessAcvitityStartIntent);
+ startAndWaitExternalActivity();
// Prepare response for first activity
CannedFillResponse response = new CannedFillResponse.Builder()
.addDataset(new CannedFillResponse.CannedDataset.Builder(
createPresentation("dataset1"))
- .setField(ID_USERNAME, "filled").build())
+ .setField(ID_USERNAME, "filled").build())
.build();
sReplier.addResponse(response);
// Trigger autofill on username
- sUiBot.selectById(USERNAME_FULL_ID);
+ mUiBot.selectById(USERNAME_FULL_ID);
// Wait for fill request to be processed
sReplier.getNextFillRequest();
// Wait until dataset1 is shown
- sUiBot.assertDatasets("dataset1");
+ mUiBot.assertDatasets("dataset1");
// Delete stopped marker
getStoppedMarker(getContext()).delete();
@@ -325,7 +361,7 @@
response = new CannedFillResponse.Builder()
.addDataset(new CannedFillResponse.CannedDataset.Builder(
createPresentation("dataset2"))
- .setField(ID_USERNAME, "filled").build())
+ .setField(ID_USERNAME, "filled").build())
.build();
sReplier.addResponse(response);
@@ -338,18 +374,18 @@
killOfProcessLoginActivityProcess();
// Trigger autofill on username in nested activity
- sUiBot.selectById(USERNAME_FULL_ID);
+ mUiBot.selectById(USERNAME_FULL_ID);
// Wait for fill request to be processed
sReplier.getNextFillRequest();
// Wait until dataset in nested activity is shown
- sUiBot.assertDatasets("dataset2");
+ mUiBot.assertDatasets("dataset2");
// Tap "Cancel".
- sUiBot.selectById(CANCEL_FULL_ID);
+ mUiBot.selectById(CANCEL_FULL_ID);
// Dataset should still be shown
- sUiBot.assertDatasets("dataset1");
+ mUiBot.assertDatasets("dataset1");
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
index 0866b2d..cbb8523 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
@@ -16,6 +16,8 @@
package android.autofillservice.cts;
import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
@@ -25,19 +27,24 @@
*/
public class SimpleSaveActivity extends AbstractAutoFillActivity {
+ private static final String TAG = "SimpleSaveActivity";
+
static final String ID_LABEL = "label";
static final String ID_INPUT = "input";
static final String ID_PASSWORD = "password";
static final String ID_COMMIT = "commit";
static final String TEXT_LABEL = "Label:";
+ private static SimpleSaveActivity sInstance;
+
TextView mLabel;
EditText mInput;
EditText mPassword;
Button mCancel;
Button mCommit;
- private static SimpleSaveActivity sInstance;
+ private boolean mAutoCommit = true;
+ private boolean mClearFieldsOnSubmit = false;
public static SimpleSaveActivity getInstance() {
return sInstance;
@@ -60,7 +67,46 @@
mCommit = findViewById(R.id.commit);
mCancel.setOnClickListener((v) -> getAutofillManager().cancel());
- mCommit.setOnClickListener((v) -> getAutofillManager().commit());
+ mCommit.setOnClickListener((v) -> onCommit());
+ }
+
+ private void onCommit() {
+ if (mClearFieldsOnSubmit) {
+ resetFields();
+ }
+ if (mAutoCommit) {
+ Log.d(TAG, "onCommit(): calling AFM.commit()");
+ getAutofillManager().commit();
+ } else {
+ Log.d(TAG, "onCommit(): NOT calling AFM.commit()");
+ }
+ }
+
+ private void resetFields() {
+ Log.d(TAG, "resetFields()");
+ mInput.setText("");
+ mPassword.setText("");
+ }
+
+ /**
+ * Defines whether the activity should automatically call {@link AutofillManager#commit()} when
+ * the commit button is tapped.
+ */
+ void setAutoCommit(boolean flag) {
+ mAutoCommit = flag;
+ }
+
+ /**
+ * Defines whether the activity should automatically clear its fields when submit is clicked.
+ */
+ void setClearFieldsOnSubmit(boolean flag) {
+ mClearFieldsOnSubmit = flag;
+ }
+
+ FillExpectation expectAutoFill(String input) {
+ final FillExpectation expectation = new FillExpectation(input, null);
+ mInput.addTextChangedListener(expectation.mInputWatcher);
+ return expectation;
}
FillExpectation expectAutoFill(String input, String password) {
@@ -76,12 +122,16 @@
private FillExpectation(String input, String password) {
mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
- mPasswordWatcher = new OneTimeTextWatcher("password", mPassword, password);
+ mPasswordWatcher = password == null
+ ? null
+ : new OneTimeTextWatcher("password", mPassword, password);
}
void assertAutoFilled() throws Exception {
mInputWatcher.assertAutoFilled();
- mPasswordWatcher.assertAutoFilled();
+ if (mPasswordWatcher != null) {
+ mPasswordWatcher.assertAutoFilled();
+ }
}
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index 9fb16a2..f10b394 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -15,7 +15,10 @@
*/
package android.autofillservice.cts;
+import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
+import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
import static android.autofillservice.cts.Helper.assertTextAndValue;
+import static android.autofillservice.cts.Helper.assertTextValue;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
@@ -26,18 +29,30 @@
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.autofillservice.cts.SimpleSaveActivity.FillExpectation;
import android.content.Intent;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.TextValueSanitizer;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject2;
import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
import org.junit.Rule;
import org.junit.Test;
+import java.util.regex.Pattern;
+
public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase {
@Rule
@@ -89,7 +104,7 @@
// Select dataset.
final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
- sUiBot.selectDataset("YO");
+ mUiBot.selectDataset("YO");
autofillExpecation.assertAutoFilled();
mActivity.syncRunOnUiThread(() -> {
@@ -97,10 +112,10 @@
mActivity.mPassword.setText("PASS");
mActivity.mCommit.performClick();
});
- final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
// Save it...
- sUiBot.saveForAutofill(saveUi, true);
+ mUiBot.saveForAutofill(saveUi, true);
// ... and assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -130,15 +145,15 @@
.build());
// Trigger autofill.
- sUiBot.assertShownByRelativeId(ID_INPUT).click();
+ mUiBot.assertShownByRelativeId(ID_INPUT).click();
sReplier.getNextFillRequest();
// Select dataset...
- sUiBot.selectDataset("YO");
+ mUiBot.selectDataset("YO");
// ...and assert autofilled values.
- final UiObject2 input = sUiBot.assertShownByRelativeId(ID_INPUT);
- final UiObject2 password = sUiBot.assertShownByRelativeId(ID_PASSWORD);
+ final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
+ final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
// TODO: password field is shown as **** ; ideally we should assert it's a password
@@ -151,8 +166,8 @@
// Trigger save...
input.setText("ID");
password.setText("PASS");
- sUiBot.assertShownByRelativeId(ID_COMMIT).click();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertShownByRelativeId(ID_COMMIT).click();
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
// ... and assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -167,11 +182,12 @@
@Test
public void testSave_afterRotation() throws Exception {
- sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+ mUiBot.setScreenOrientation(UiBot.PORTRAIT);
try {
saveTest(true);
} finally {
- sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+ mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+ cleanUpAfterScreenOrientationIsBackToPortrait();
}
}
@@ -196,15 +212,18 @@
mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
- UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
if (rotate) {
- sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
- saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ // After the device rotates, the input field get focus and generate a new session.
+ sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+ mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+ saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
}
// Save it...
- sUiBot.saveForAutofill(saveUi, true);
+ mUiBot.saveForAutofill(saveUi, true);
// ... and assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -212,6 +231,37 @@
}
@Test
+ public void testSave_launchIntent() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"));
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+ Helper.assertHasSessions(mPackageName);
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("108");
+ mActivity.mCommit.performClick();
+ });
+
+ // Save it...
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ sReplier.getNextSaveRequest();
+ // ... and assert activity was launched
+ WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
+ }
+
+ @Test
public void testSaveThenStartNewSessionRightAway() throws Exception {
startActivity();
@@ -236,7 +286,7 @@
});
// Make sure Save UI for 1st session was canceled....
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
//... and 2nd session canceled as well.
Helper.assertNoDanglingSessions();
@@ -270,7 +320,7 @@
});
// Assert it's not showing.
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
}
@Test
@@ -304,7 +354,7 @@
// Then launches the main activity.
startActivity(true);
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// And finally test it..
dismissSaveTest(DismissType.RECENTS_BUTTON);
@@ -329,31 +379,31 @@
mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
// Then make sure it goes away when user doesn't want it..
switch (dismissType) {
case BACK_BUTTON:
- sUiBot.pressBack();
+ mUiBot.pressBack();
break;
case HOME_BUTTON:
- sUiBot.pressHome();
+ mUiBot.pressHome();
break;
case TOUCH_OUTSIDE:
- sUiBot.assertShownByText(TEXT_LABEL).click();
+ mUiBot.assertShownByText(TEXT_LABEL).click();
break;
case FOCUS_OUTSIDE:
mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
- sUiBot.assertShownByText(TEXT_LABEL).click();
+ mUiBot.assertShownByText(TEXT_LABEL).click();
break;
case RECENTS_BUTTON:
- sUiBot.switchAppsUsingRecents();
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+ mUiBot.switchAppsUsingRecents();
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
break;
default:
throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
}
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
}
@Test
@@ -372,25 +422,25 @@
.build());
// Trigger autofill.
- sUiBot.assertShownByRelativeId(ID_INPUT).click();
+ mUiBot.assertShownByRelativeId(ID_INPUT).click();
sReplier.getNextFillRequest();
- sUiBot.assertDatasets("YO");
+ mUiBot.assertDatasets("YO");
callback.assertUiShownEvent(mActivity.mInput);
// Go home, you are drunk!
- sUiBot.pressHome();
- sUiBot.assertNoDatasets();
+ mUiBot.pressHome();
+ mUiBot.assertNoDatasets();
callback.assertUiHiddenEvent(mActivity.mInput);
// Switch back to the activity.
restartActivity();
- sUiBot.assertShownByText(TEXT_LABEL, Helper.ACTIVITY_RESURRECTION_MS);
- final UiObject2 datasetPicker = sUiBot.assertDatasets("YO");
+ mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("YO");
callback.assertUiShownEvent(mActivity.mInput);
// Now autofill it.
final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
- sUiBot.selectDataset(datasetPicker, "YO");
+ mUiBot.selectDataset(datasetPicker, "YO");
autofillExpecation.assertAutoFilled();
}
@@ -407,18 +457,18 @@
// Trigger autofill.
mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Trigger save, but don't tap it.
mActivity.syncRunOnUiThread(() -> {
mActivity.mInput.setText("108");
mActivity.mCommit.performClick();
});
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
// Go home, you are drunk!
- sUiBot.pressHome();
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.pressHome();
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
Helper.assertNoDanglingSessions();
// Prepare the response for the next session, which will be automatically triggered
@@ -434,15 +484,15 @@
// Switch back to the activity.
restartActivity();
- sUiBot.assertShownByText(TEXT_LABEL, Helper.ACTIVITY_RESURRECTION_MS);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
sReplier.getNextFillRequest();
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Trigger and select UI.
mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
- sUiBot.selectDataset("YO");
+ mUiBot.selectDataset("YO");
// Assert it.
autofillExpecation.assertAutoFilled();
@@ -453,7 +503,7 @@
intent.setFlags(
Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
mContext.startActivity(intent);
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
}
@Override
@@ -485,34 +535,44 @@
tapSaveUiLink(saveUi);
// Make sure new activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
// .. then do something to return to previous activity...
switch (type) {
case ROTATE_THEN_TAP_BACK_BUTTON:
- sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+ // After the device rotates, the input field get focus and generate a new session.
+ sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+ mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
// not breaking on purpose
case TAP_BACK_BUTTON:
// ..then go back and save it.
- sUiBot.pressBack();
+ mUiBot.pressBack();
break;
case FINISH_ACTIVITY:
// ..then finishes it.
- WelcomeActivity.finishIt();
+ WelcomeActivity.finishIt(mUiBot);
break;
default:
throw new IllegalArgumentException("invalid type: " + type);
}
// Make sure previous activity is back...
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT);
// ... and tap save.
final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
- sUiBot.saveForAutofill(newSaveUi, true);
+ mUiBot.saveForAutofill(newSaveUi, true);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+
+ }
+
+ @Override
+ protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+ sReplier.getNextFillRequest();
}
@Override
@@ -544,27 +604,27 @@
tapSaveUiLink(saveUi);
// Make sure new activity is shown.
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
// Tap back to restore the Save UI...
- sUiBot.pressBack();
+ mUiBot.pressBack();
// Make sure previous activity is back...
- sUiBot.assertShownByRelativeId(ID_LABEL);
+ mUiBot.assertShownByRelativeId(ID_LABEL);
// ...but don't tap it...
- final UiObject2 saveUi2 = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
// ...instead, do something to dismiss it:
switch (action) {
case TOUCH_OUTSIDE:
- sUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+ mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
break;
case TAP_NO_ON_SAVE_UI:
- sUiBot.saveForAutofill(saveUi2, false);
+ mUiBot.saveForAutofill(saveUi2, false);
break;
case TAP_YES_ON_SAVE_UI:
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
Helper.assertNoDanglingSessions();
@@ -572,7 +632,7 @@
default:
throw new IllegalArgumentException("invalid action: " + action);
}
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
// Make sure previous session was finished.
Helper.assertNoDanglingSessions();
@@ -599,7 +659,7 @@
});
// Save it...
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
// ... and assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
@@ -634,12 +694,12 @@
// Tap the link.
tapSaveUiLink(saveUi);
// Make sure new activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
switch (type) {
case TAP_RECENTS:
- sUiBot.switchAppsUsingRecents();
+ mUiBot.switchAppsUsingRecents();
break;
case LAUNCH_PREVIOUS_ACTIVITY:
startActivity(SimpleSaveActivity.class);
@@ -647,17 +707,75 @@
case LAUNCH_NEW_ACTIVITY:
// Launch a 3rd activity...
startActivity(LoginActivity.class);
- sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+ mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
// ...then go back
- sUiBot.pressBack();
+ mUiBot.pressBack();
break;
default:
throw new IllegalArgumentException("invalid type: " + type);
}
// Make sure right activity is showing
- sUiBot.assertShownByRelativeId(ID_INPUT);
+ mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ }
+
+ @Test
+ public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+ // Added on reversed order on purpose
+ .addDataset(new CannedDataset.Builder()
+ .setId("D2")
+ .setField(ID_INPUT, "id again")
+ .setField(ID_PASSWORD, "pass")
+ .setPresentation(createPresentation("D2"))
+ .build())
+ .addDataset(new CannedDataset.Builder()
+ .setId("D1")
+ .setField(ID_INPUT, "id")
+ .setPresentation(createPresentation("D1"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+
+ // Select 1st dataset.
+ final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
+ final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
+ mUiBot.selectDataset(picker1, "D1");
+ autofillExpecation1.assertAutoFilled();
+
+ // Select 2nd dataset.
+ mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+ final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
+ final UiObject2 picker2 = mUiBot.assertDatasets("D2");
+ mUiBot.selectDataset(picker2, "D2");
+ autofillExpecation2.assertAutoFilled();
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("ID");
+ mActivity.mPassword.setText("PASS");
+ mActivity.mCommit.performClick();
+ });
+ final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // Save it...
+ mUiBot.saveForAutofill(saveUi, true);
+
+ // ... and assert results
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+ assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+ assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
}
@Override
@@ -693,14 +811,14 @@
tapSaveUiLink(saveUi);
// Make sure new activity is shown...
- WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
// Save UI should be showing as well, since Trampoline finished.
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
// Go back and make sure it's showing the right activity.
- sUiBot.pressBack();
- sUiBot.assertShownByRelativeId(ID_LABEL);
+ mUiBot.pressBack();
+ mUiBot.assertShownByRelativeId(ID_LABEL);
// Now start a new session.
sReplier.addResponse(new CannedFillResponse.Builder()
@@ -712,9 +830,327 @@
mActivity.mPassword.setText("42");
mActivity.mCommit.performClick();
});
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
}
+
+ @Test
+ public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
+ startActivity();
+
+ // Set listeners that will change the saved value
+ new AntiTrimmerTextWatcher(mActivity.mInput);
+ new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final AutofillId inputId = mActivity.mInput.getAutofillId();
+ final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, passwordId)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+ Helper.assertHasSessions(mPackageName);
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("id");
+ mActivity.mPassword.setText("pass");
+ mActivity.mCommit.performClick();
+ });
+
+ // Save it...
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+ // ... and assert results
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+ assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+ }
+
+ @Test
+ public void testSanitizeOnSaveNoChange() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final AutofillId inputId = mActivity.mInput.getAutofillId();
+ final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .setOptionalSavableIds(ID_PASSWORD)
+ .addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, passwordId)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+ mUiBot.assertNoDatasets();
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("#id#");
+ mActivity.mPassword.setText("#pass#");
+ mActivity.mCommit.performClick();
+ });
+
+ // Save it...
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+ // ... and assert results
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+ assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+ }
+
+ @Test
+ public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
+ startActivity();
+
+ // Set listeners that will change the saved value
+ new AntiTrimmerTextWatcher(mActivity.mInput);
+ new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final AutofillId inputId = mActivity.mInput.getAutofillId();
+ final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+ .addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, passwordId)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_INPUT, "id")
+ .setField(ID_PASSWORD, "pass")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("id");
+ mActivity.mPassword.setText("pass");
+ mActivity.mCommit.performClick();
+ });
+
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ }
+
+ @Test
+ public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .setOptionalSavableIds(ID_PASSWORD)
+ .addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"), passwordId)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_INPUT, "id")
+ .setField(ID_PASSWORD, "pass")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("id");
+ mActivity.mPassword.setText("#pass#");
+ mActivity.mCommit.performClick();
+ });
+
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ }
+
+ @Test
+ public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final AutofillId inputId = mActivity.mInput.getAutofillId();
+ final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+ .addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+ inputId, passwordId)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_INPUT, "#id#")
+ .setField(ID_PASSWORD, "#pass#")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("id");
+ mActivity.mPassword.setText("pass");
+ mActivity.mCommit.performClick();
+ });
+
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ }
+
+ @Test
+ public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
+ startActivity();
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final AutofillId inputId = mActivity.mInput.getAutofillId();
+ final AutofillId passwordId = mActivity.mPassword.getAutofillId();
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .setOptionalSavableIds(ID_PASSWORD)
+ .addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+ inputId, passwordId)
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_INPUT, "id")
+ .setField(ID_PASSWORD, "#pass#")
+ .setPresentation(createPresentation("YO"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("id");
+ mActivity.mPassword.setText("pass");
+ mActivity.mCommit.performClick();
+ });
+
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ }
+
+ @Test
+ public void testExplicitySaveButton() throws Exception {
+ explicitySaveButtonTest(false, 0);
+ }
+
+ @Test
+ public void testExplicitySaveButtonWhenAppClearFields() throws Exception {
+ explicitySaveButtonTest(true, 0);
+ }
+
+ @Test
+ public void testExplicitySaveButtonOnly() throws Exception {
+ explicitySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
+ }
+
+ /**
+ * Tests scenario where service explicitly indicates which button is used to save.
+ */
+ private void explicitySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
+ startActivity();
+ mActivity.setAutoCommit(false);
+ mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
+
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .setSaveTriggerId(mActivity.mCommit.getAutofillId())
+ .setSaveInfoFlags(flags)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+ Helper.assertHasSessions(mPackageName);
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("108");
+ mActivity.mCommit.performClick();
+ });
+ UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // Save it...
+ mUiBot.saveForAutofill(saveUi, true);
+
+ // ... and assert results
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+ }
+
+ @Override
+ protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+ startActivity();
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ final CustomDescription.Builder customDescription =
+ newCustomDescriptionBuilder(WelcomeActivity.class);
+ final RemoteViews update = newTemplate();
+ if (updateLinkView) {
+ update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+ } else {
+ update.setCharSequence(R.id.static_text, "setText", "ME!");
+ }
+ Validator validCondition = new RegexValidator(mActivity.mInput.getAutofillId(),
+ Pattern.compile(".*"));
+ customDescription.batchUpdate(validCondition,
+ new BatchUpdates.Builder().updateTemplate(update).build());
+
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setCustomDescription(customDescription.build())
+ .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+ .build());
+
+ // Trigger autofill.
+ mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+ sReplier.getNextFillRequest();
+ Helper.assertHasSessions(mPackageName);
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("108");
+ mActivity.mCommit.performClick();
+ });
+ final UiObject2 saveUi;
+ if (updateLinkView) {
+ saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
+ } else {
+ saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+ final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+ assertThat(changed.getText()).isEqualTo("ME!");
+ }
+
+ // Tap the link.
+ tapSaveUiLink(saveUi);
+
+ // Make sure new activity is shown...
+ WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
new file mode 100644
index 0000000..572e3b1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.TextValueSanitizer;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class TextValueSanitizerTest {
+
+ @Test
+ public void testConstructor_nullValues() {
+ assertThrows(NullPointerException.class,
+ () -> new TextValueSanitizer(Pattern.compile("42"), null));
+ assertThrows(NullPointerException.class,
+ () -> new TextValueSanitizer(null, "42"));
+ }
+
+ @Test
+ public void testSanitize_nullValue() {
+ final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+ assertThat(sanitizer.sanitize(null)).isNull();
+ }
+
+ @Test
+ public void testSanitize_nonTextValue() {
+ final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+ final AutofillValue value = AutofillValue.forToggle(true);
+ assertThat(sanitizer.sanitize(value)).isNull();
+ }
+
+ @Test
+ public void testSanitize_badRegex() {
+ final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+ "$2"); // invalid group
+ final AutofillValue value = AutofillValue.forText("blah 42 blaH");
+ assertThat(sanitizer.sanitize(value)).isNull();
+ }
+
+ @Test
+ public void testSanitize_valueMismatch() {
+ final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "xxx");
+ final AutofillValue value = AutofillValue.forText("43");
+ assertThat(sanitizer.sanitize(value)).isNull();
+ }
+
+ @Test
+ public void testSanitize_simpleMatch() {
+ final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"),
+ "forty-two");
+ assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+ .isEqualTo("forty-two");
+ }
+
+ @Test
+ public void testSanitize_multipleMatches() {
+ final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+ "Number");
+ assertThat(sanitizer.sanitize(AutofillValue.forText("blah 42 blaH")).getTextValue())
+ .isEqualTo("NumberNumber");
+ }
+
+ @Test
+ public void testSanitize_groupSubstitutionMatch() {
+ final TextValueSanitizer sanitizer =
+ new TextValueSanitizer(Pattern.compile("\\s*(\\d*)\\s*"), "$1");
+ assertThat(sanitizer.sanitize(AutofillValue.forText(" 42 ")).getTextValue())
+ .isEqualTo("42");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
index d4be2e6..4bd8cf0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
@@ -31,7 +31,6 @@
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.icu.util.Calendar;
-import org.junit.After;
import org.junit.Test;
/**
@@ -42,11 +41,6 @@
protected abstract T getTimePickerActivity();
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
@Test
public void testAutoFillAndSave() throws Exception {
final T activity = getTimePickerActivity();
@@ -78,7 +72,7 @@
assertTextIsSanitized(fillRequest.structure, ID_TIME_PICKER);
assertNumberOfChildren(fillRequest.structure, ID_TIME_PICKER, 0);
// Auto-fill it.
- sUiBot.selectDataset("Adventure Time");
+ mUiBot.selectDataset("Adventure Time");
// Check the results.
activity.assertAutoFilled();
@@ -87,7 +81,7 @@
activity.setTime(10, 40);
activity.tapOk();
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeout.java b/tests/autofillservice/src/android/autofillservice/cts/Timeout.java
new file mode 100644
index 0000000..e709dac
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeout.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts;
+
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A "smart" timeout that supports exponential backoff.
+ */
+//TODO: move to common CTS Code
+public final class Timeout {
+
+ private static final String TAG = "Timeout";
+ private static final boolean VERBOSE = true;
+
+ private final String mName;
+ private long mCurrentValue;
+ private final float mMultiplier;
+ private final long mMaxValue;
+
+ /**
+ * Default constructor.
+ *
+ * @param name name to be used for logging purposes.
+ * @param initialValue initial timeout value, in ms.
+ * @param multiplier multiplier for {@link #increase()}.
+ * @param maxValue max timeout value (in ms) set by {@link #increase()}.
+ *
+ * @throws IllegalArgumentException if {@code name} is {@code null} or empty,
+ * {@code initialValue}, {@code multiplir} or {@code maxValue} are less than {@code 1},
+ * or if {@code initialValue} is higher than {@code maxValue}
+ */
+ public Timeout(String name, long initialValue, float multiplier, long maxValue) {
+ if (initialValue < 1 || maxValue < 1 || initialValue > maxValue) {
+ throw new IllegalArgumentException(
+ "invalid initial and/or max values: " + initialValue + " and " + maxValue);
+ }
+ if (multiplier <= 1) {
+ throw new IllegalArgumentException("multiplier must be higher than 1: " + multiplier);
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("no name");
+ }
+ mName = name;
+ mCurrentValue = initialValue;
+ mMultiplier = multiplier;
+ mMaxValue = maxValue;
+ Log.d(TAG, "Constructor: " + this + " at " + JUnitHelper.getCurrentTestName());
+ }
+
+ /**
+ * Gets the current timeout, in ms.
+ */
+ public long ms() {
+ return mCurrentValue;
+ }
+
+ /**
+ * Gets the max timeout, in ms.
+ */
+ public long getMaxValue() {
+ return mMaxValue;
+ }
+
+ /**
+ * @return the mMultiplier
+ */
+ public float getMultiplier() {
+ return mMultiplier;
+ }
+
+ /**
+ * Gets the user-friendly name of this timeout.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Increases the current value by the {@link #getMultiplier()}, up to {@link #getMaxValue()}.
+ *
+ * @return previous current value.
+ */
+ public long increase() {
+ final long oldValue = mCurrentValue;
+ mCurrentValue = Math.min(mMaxValue, (long) (mCurrentValue * mMultiplier));
+ if (oldValue != mCurrentValue) {
+ Log.w(TAG, mName + " increased from " + oldValue + "ms to " + mCurrentValue + "ms at "
+ + JUnitHelper.getCurrentTestName());
+ }
+ return oldValue;
+ }
+
+ /**
+ * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
+ * {@link #ms()}.
+ *
+ * @param description description of the job for logging purposes.
+ * @param job job to be run, must return {@code null} if it failed and should be retried.
+ * @throws RetryableException if all attempts failed.
+ * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
+ * {@code job} is {@code null}, or if {@code maxAttempts} is less than 1.
+ * @throws Exception any other exception thrown by helper methods.
+ *
+ * @return job's result.
+ */
+ public <T> T run(String description, Callable<T> job) throws Exception {
+ return run(description, 100, job);
+ }
+
+ /**
+ * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
+ * {@link #ms()}.
+ *
+ * @param description description of the job for logging purposes.
+ * @param job job to be run, must return {@code null} if it failed and should be retried.
+ * @param retryMs how long to sleep between failures.
+ * @throws RetryableException if all attempts failed.
+ * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
+ * {@code job} is {@code null}, or if {@code maxAttempts} is less than 1.
+ * @throws Exception any other exception thrown by helper methods.
+ *
+ * @return job's result.
+ */
+ public <T> T run(String description, long retryMs, Callable<T> job) throws Exception {
+ if (TextUtils.isEmpty(description)) {
+ throw new IllegalArgumentException("no description");
+ }
+ if (job == null) {
+ throw new IllegalArgumentException("no job");
+ }
+ if (retryMs < 1) {
+ throw new IllegalArgumentException("need to sleep at least 1ms, right?");
+ }
+ long startTime = System.currentTimeMillis();
+ int attempt = 0;
+ while (System.currentTimeMillis() - startTime <= mCurrentValue) {
+ final T result = job.call();
+ if (result != null) {
+ // Good news, everyone: job succeeded on first attempt!
+ return result;
+ }
+ attempt++;
+ if (VERBOSE) {
+ Log.v(TAG, description + " failed at attempt #" + attempt + "; sleeping for "
+ + retryMs + "ms before trying again");
+ }
+ SystemClock.sleep(retryMs);
+ retryMs *= mMultiplier;
+ }
+ Log.w(TAG, description + " failed after " + attempt + " attempts and "
+ + (System.currentTimeMillis() - startTime) + "ms: " + this);
+ throw new RetryableException(this, description);
+ }
+
+ @Override
+ public String toString() {
+ return mName + ": [current=" + mCurrentValue + "ms; multiplier=" + mMultiplier + "x; max="
+ + mMaxValue + "ms]";
+ }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
new file mode 100644
index 0000000..69c69e4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/TimeoutTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.assertFloat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.concurrent.Callable;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TimeoutTest {
+
+ private static final String NAME = "TIME, Y U NO OUT?";
+ private static final String DESC = "something";
+
+ @Mock
+ private Callable<Object> mJob;
+
+ @Test
+ public void testInvalidConstructor() {
+ // Invalid name
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(null, 1, 2, 2));
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout("", 1, 2, 2));
+ // Invalid initial value
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, -1, 2, 2));
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 0, 2, 2));
+ // Invalid multiplier
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, -1, 2));
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 0, 2));
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 1, 2));
+ // Invalid max value
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, -1));
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, 0));
+ // Max value cannot be less than initial
+ assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 2, 2, 1));
+ }
+
+ @Test
+ public void testGetters() {
+ final Timeout timeout = new Timeout(NAME, 1, 2, 5);
+ assertThat(timeout.ms()).isEqualTo(1);
+ assertFloat(timeout.getMultiplier(), 2);
+ assertThat(timeout.getMaxValue()).isEqualTo(5);
+ assertThat(timeout.getName()).isEqualTo(NAME);
+ }
+
+ @Test
+ public void testIncrease() {
+ final Timeout timeout = new Timeout(NAME, 1, 2, 5);
+ // Pre-maximum
+ assertThat(timeout.increase()).isEqualTo(1);
+ assertThat(timeout.ms()).isEqualTo(2);
+ assertThat(timeout.increase()).isEqualTo(2);
+ assertThat(timeout.ms()).isEqualTo(4);
+ // Post-maximum
+ assertThat(timeout.increase()).isEqualTo(4);
+ assertThat(timeout.ms()).isEqualTo(5);
+ assertThat(timeout.increase()).isEqualTo(5);
+ assertThat(timeout.ms()).isEqualTo(5);
+ }
+
+ @Test
+ public void testRun_invalidArgs() {
+ final Timeout timeout = new Timeout(NAME, 1, 2, 5);
+ // Invalid description
+ assertThrows(IllegalArgumentException.class, ()-> timeout.run(null, mJob));
+ assertThrows(IllegalArgumentException.class, ()-> timeout.run("", mJob));
+ // Invalid max attempts
+ assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, -1, mJob));
+ assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, 0, mJob));
+ // Invalid job
+ assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, null));
+ }
+
+ @Test
+ public void testRun_successOnFirstAttempt() throws Exception {
+ final Timeout timeout = new Timeout(NAME, 100, 2, 500);
+ final Object result = new Object();
+ when(mJob.call()).thenReturn(result);
+ assertThat(timeout.run(DESC, 1, mJob)).isSameAs(result);
+ }
+
+ @Test
+ public void testRun_successOnSecondAttempt() throws Exception {
+ final Timeout timeout = new Timeout(NAME, 100, 2, 500);
+ final Object result = new Object();
+ when(mJob.call()).thenReturn((Object) null, result);
+ assertThat(timeout.run(DESC, 10, mJob)).isSameAs(result);
+ }
+
+ @Test
+ public void testRun_allAttemptsFailed() throws Exception {
+ final Timeout timeout = new Timeout(NAME, 100, 2, 500);
+ final RetryableException e = expectThrows(RetryableException.class,
+ () -> timeout.run(DESC, 10, mJob));
+ assertThat(e.getMessage()).contains(DESC);
+ assertThat(e.getTimeout()).isSameAs(timeout);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
new file mode 100644
index 0000000..6111cf6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+/**
+ * Timeouts for common tasks.
+ */
+final class Timeouts {
+
+ /**
+ * Timeout until framework binds / unbinds from service.
+ */
+ static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT", 1000, 2F, 2000);
+
+ /**
+ * Timeout until framework unbinds from a service.
+ */
+ static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT", 5000, 2F, 10000);
+
+ /**
+ * Timeout to get the expected number of fill events.
+ */
+ static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT", 1000, 2F, 10000);
+
+ /**
+ * Timeout for expected autofill requests.
+ */
+ static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", 1000, 2F, 2000);
+
+ /**
+ * Timeout for expected save requests.
+ */
+ static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", 1000, 2F, 5000);
+
+ /**
+ * Time to wait if a UI change is not expected
+ */
+ static final Timeout NOT_SHOWING_TIMEOUT = new Timeout("NOT_SHOWING_TIMEOUT", 100, 2F, 500);
+
+ /**
+ * Timeout for UI operations. Typically used by {@link UiBot}.
+ */
+ static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT", 500, 2F, 2000);
+
+ /**
+ * Timeout for showing the autofill dataset picker UI.
+ *
+ * <p>The value is usually higher than {@link #UI_TIMEOUT} because the performance of the
+ * dataset picker UI can be affect by external factors in some low-level devices.
+ *
+ * <p>Typically used by {@link UiBot}.
+ */
+ static final Timeout UI_DATASET_PICKER_TIMEOUT =
+ new Timeout("UI_DATASET_PICKER_TIMEOUT", 500, 2F, 4000);
+
+ /**
+ * Timeout (in milliseconds) for an activity to be brought out to top.
+ */
+ static final Timeout ACTIVITY_RESURRECTION =
+ new Timeout("ACTIVITY_RESURRECTION", 6000, 1.5F, 20000);
+
+ /**
+ * Timeout for changing the screen orientation.
+ */
+ static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT =
+ new Timeout("UI_SCREEN_ORIENTATION_TIMEOUT", 5000, 2F, 10000);
+
+ /**
+ * Timeout for using Recents to swtich activities.
+ */
+ static final Timeout UI_RECENTS_SWITCH_TIMEOUT =
+ new Timeout("UI_RECENTS_SWITCH_TIMEOUT", 200, 2F, 1000);
+
+ private Timeouts() {
+ throw new UnsupportedOperationException("contain static methods only");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 5611499..d4f77ca 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -16,9 +16,12 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.NOT_SHOWING_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.SAVE_TIMEOUT_MS;
-import static android.autofillservice.cts.Helper.UI_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.NOT_SHOWING_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.UI_DATASET_PICKER_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.UI_RECENTS_SWITCH_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.UI_SCREEN_ORIENTATION_TIMEOUT;
+import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
@@ -33,32 +36,39 @@
import android.app.UiAutomation;
import android.content.Context;
import android.content.res.Resources;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.service.autofill.SaveInfo;
+import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
import android.text.Html;
import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityWindowInfo;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.TimeoutException;
/**
* Helper for UI-related needs.
*/
final class UiBot {
+ private static final String TAG = "AutoFillCtsUiBot";
+
private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
private static final String RESOURCE_ID_CONTEXT_MENUITEM = "floating_toolbar_menu_item_text";
+ private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
@@ -70,13 +80,21 @@
private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
"autofill_save_type_email_address";
+ private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "save_password_notnow";
+ private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
+
private static final String RESOURCE_STRING_AUTOFILL = "autofill";
private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
"autofill_picker_accessibility_title";
private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
"autofill_save_accessibility_title";
- private static final String TAG = "AutoFillCtsUiBot";
+ private static final BySelector DATASET_PICKER_SELECTOR = By.res("android",
+ RESOURCE_ID_DATASET_PICKER);
+ private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
+
+ private static final boolean DONT_DUMP_ON_ERROR = false;
+ private static final boolean DUMP_ON_ERROR = true;
/** Pass to {@link #setScreenOrientation(int)} to change the display to portrait mode */
public static int PORTRAIT = 0;
@@ -84,13 +102,19 @@
/** Pass to {@link #setScreenOrientation(int)} to change the display to landscape mode */
public static int LANDSCAPE = 1;
-
private final UiDevice mDevice;
private final Context mContext;
private final String mPackageName;
private final UiAutomation mAutoman;
+ private final Timeout mDefaultTimeout;
- UiBot(Instrumentation instrumentation) throws Exception {
+ UiBot() {
+ this(UI_TIMEOUT);
+ }
+
+ UiBot(Timeout defaultTimeout) {
+ mDefaultTimeout = defaultTimeout;
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mDevice = UiDevice.getInstance(instrumentation);
mContext = instrumentation.getContext();
mPackageName = mContext.getPackageName();
@@ -100,16 +124,8 @@
/**
* Asserts the dataset chooser is not shown.
*/
- void assertNoDatasets() {
- final UiObject2 picker;
- try {
- picker = findDatasetPicker(NOT_SHOWING_TIMEOUT_MS);
- } catch (Throwable t) {
- // Use a more elegant check than catching the expection because it's not showing...
- return;
- }
- throw new RetryableException(
- "Should not be showing datasets, but got " + getChildrenAsText(picker));
+ void assertNoDatasets() throws Exception {
+ assertNotShowing("datasets", DATASET_PICKER_SELECTOR, NOT_SHOWING_TIMEOUT);
}
/**
@@ -117,10 +133,31 @@
*
* @return the dataset picker object.
*/
- UiObject2 assertDatasets(String...names) {
- final UiObject2 picker = findDatasetPicker();
+ UiObject2 assertDatasets(String...names) throws Exception {
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
- .containsExactlyElementsIn(Arrays.asList(names));
+ .containsExactlyElementsIn(Arrays.asList(names)).inOrder();
+ return picker;
+ }
+
+ /**
+ * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
+ *
+ * @return the dataset picker object.
+ */
+ UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
+ throws Exception {
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+ final List<String> expectedChild = new ArrayList<>();
+ if (header != null) {
+ expectedChild.add(header);
+ }
+ expectedChild.addAll(Arrays.asList(names));
+ if (footer != null) {
+ expectedChild.add(footer);
+ }
+ assertWithMessage("wrong elements on dataset picker").that(getChildrenAsText(picker))
+ .containsExactlyElementsIn(expectedChild).inOrder();
return picker;
}
@@ -146,8 +183,8 @@
/**
* Selects a dataset that should be visible in the floating UI.
*/
- void selectDataset(String name) {
- final UiObject2 picker = findDatasetPicker();
+ void selectDataset(String name) throws Exception {
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
selectDataset(picker, name);
}
@@ -168,7 +205,7 @@
* <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
* {@link #selectDataset(String)}.
*/
- void selectByText(String name) {
+ void selectByText(String name) throws Exception {
Log.v(TAG, "selectByText(): " + name);
final UiObject2 object = waitForObject(By.text(name));
@@ -181,12 +218,12 @@
* <p><b>NOTE:</b> when asserting the dataset picker is shown, prefer
* {@link #assertDatasets(String...)}.
*/
- public UiObject2 assertShownByText(String text) {
- return assertShownByText(text, UI_TIMEOUT_MS);
+ public UiObject2 assertShownByText(String text) throws Exception {
+ return assertShownByText(text, mDefaultTimeout);
}
- public UiObject2 assertShownByText(String text, int timeoutMs) {
- final UiObject2 object = waitForObject(By.text(text), timeoutMs);
+ public UiObject2 assertShownByText(String text, Timeout timeout) throws Exception {
+ final UiObject2 object = waitForObject(By.text(text), timeout);
assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
return object;
}
@@ -195,7 +232,7 @@
* Asserts a node with the given content description is shown.
*
*/
- public UiObject2 assertShownByContentDescription(String contentDescription) {
+ public UiObject2 assertShownByContentDescription(String contentDescription) throws Exception {
final UiObject2 object = waitForObject(By.desc(contentDescription));
assertWithMessage("No node with content description '%s'", contentDescription).that(object)
.isNotNull();
@@ -214,33 +251,68 @@
/**
* Selects a view by id.
*/
- void selectById(String id) {
+ void selectById(String id) throws Exception {
Log.v(TAG, "selectById(): " + id);
- final UiObject2 view = waitForObject(By.res(id));
+ final UiObject2 view = waitForObject(By.res(id), mDefaultTimeout);
view.click();
}
/**
* Asserts the id is shown on the screen.
*/
- void assertShownById(String id) {
+ void assertShownById(String id) throws Exception {
assertThat(waitForObject(By.res(id))).isNotNull();
}
/**
* Asserts the id is shown on the screen, using a resource id from the test package.
*/
- UiObject2 assertShownByRelativeId(String id) {
- final UiObject2 obj = waitForObject(By.res(mPackageName, id));
+ UiObject2 assertShownByRelativeId(String id) throws Exception {
+ return assertShownByRelativeId(id, mDefaultTimeout);
+ }
+
+ UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
+ final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
assertThat(obj).isNotNull();
return obj;
}
+ /**
+ * Asserts the id is not shown on the screen anymore, using a resource id from the test package.
+ *
+ * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
+ * it might pass without really asserting anything.
+ */
+ void assertGoneByRelativeId(String id, Timeout timeout) {
+ boolean gone = mDevice.wait(Until.gone(By.res(mPackageName, id)), timeout.ms());
+ if (!gone) {
+ final String message = "Object with id '" + id + "' should be gone after "
+ + timeout + " ms";
+ dumpScreen(message);
+ throw new RetryableException(message);
+ }
+ }
+
+ /**
+ * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
+ */
+ private void assertNotShowing(String description, BySelector selector, Timeout timeout)
+ throws Exception {
+ final UiObject2 object;
+ try {
+ object = waitForObject(null, selector, timeout, DONT_DUMP_ON_ERROR);
+ } catch (RetryableException t) {
+ // Not found as expected.
+ return;
+ }
+ throw new RetryableException(timeout, "Should not be showing %s, but got %s",
+ description, getChildrenAsText(object));
+ }
/**
* Gets the text set on a view.
*/
- String getTextById(String id) {
+ String getTextById(String id) throws Exception {
final UiObject2 obj = waitForObject(By.res(id));
return obj.getText();
}
@@ -248,14 +320,14 @@
/**
* Focus in the view with the given resource id.
*/
- void focusByRelativeId(String id) {
+ void focusByRelativeId(String id) throws Exception {
waitForObject(By.res(mPackageName, id)).click();
}
/**
* Sets a new text on a view.
*/
- void setTextById(String id, String newText) {
+ void setTextById(String id, String newText) throws Exception {
UiObject2 view = waitForObject(By.res(id));
view.setText(newText);
}
@@ -263,14 +335,14 @@
/**
* Asserts the save snackbar is showing and returns it.
*/
- UiObject2 assertSaveShowing(int type) {
- return assertSaveShowing(SAVE_TIMEOUT_MS, type);
+ UiObject2 assertSaveShowing(int type) throws Exception {
+ return assertSaveShowing(SAVE_TIMEOUT, type);
}
/**
* Asserts the save snackbar is showing and returns it.
*/
- UiObject2 assertSaveShowing(long timeout, int type) {
+ UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
return assertSaveShowing(null, timeout, type);
}
@@ -293,11 +365,16 @@
/**
* Uses the Recents button to switch back to previous activity
*/
- void switchAppsUsingRecents() throws RemoteException {
+ void switchAppsUsingRecents() throws Exception {
Log.d(TAG, "switchAppsUsingRecents()");
// Press once to show list of apps...
mDevice.pressRecentApps();
+
+ // ...wait until apps are shown...
+ // TODO(b/37566627): figure out a way to wait for a specific UI instead.
+ SystemClock.sleep(UI_RECENTS_SWITCH_TIMEOUT.ms());
+
// ...press again to go back to the activity.
mDevice.pressRecentApps();
}
@@ -305,15 +382,8 @@
/**
* Asserts the save snackbar is not showing and returns it.
*/
- void assertSaveNotShowing(int type) {
- try {
- assertSaveShowing(NOT_SHOWING_TIMEOUT_MS, type);
- } catch (Throwable t) {
- // TODO: use a more elegant check than catching the expection because it's not showing
- // (in which case it wouldn't need a type as parameter).
- return;
- }
- throw new RetryableException("snack bar is showing");
+ void assertSaveNotShowing(int type) throws Exception {
+ assertNotShowing("save UI for type " + type, SAVE_UI_SELECTOR, NOT_SHOWING_TIMEOUT);
}
private String getSaveTypeString(int type) {
@@ -340,33 +410,33 @@
return getString(typeResourceName);
}
- UiObject2 assertSaveShowing(String description, int... types) {
+ UiObject2 assertSaveShowing(String description, int... types) throws Exception {
return assertSaveShowing(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, description,
- SAVE_TIMEOUT_MS, types);
+ SAVE_TIMEOUT, types);
}
- UiObject2 assertSaveShowing(String description, long timeout, int... types) {
+ UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
+ throws Exception {
return assertSaveShowing(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, description, timeout,
types);
}
UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
- int... types) {
- return assertSaveShowing(negativeButtonStyle, description, SAVE_TIMEOUT_MS, types);
+ int... types) throws Exception {
+ return assertSaveShowing(negativeButtonStyle, description, SAVE_TIMEOUT, types);
}
- UiObject2 assertSaveShowing(int negativeButtonStyle, String description, long timeout,
- int... types) {
- final UiObject2 snackbar = waitForObject(By.res("android", RESOURCE_ID_SAVE_SNACKBAR),
- timeout);
+ UiObject2 assertSaveShowing(int negativeButtonStyle, String description, Timeout timeout,
+ int... types) throws Exception {
+ final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
final UiObject2 titleView =
- waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), UI_TIMEOUT_MS);
+ waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), timeout);
assertWithMessage("save title (%s) is not shown", RESOURCE_ID_SAVE_TITLE).that(titleView)
.isNotNull();
final UiObject2 iconView =
- waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), UI_TIMEOUT_MS);
+ waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), timeout);
assertWithMessage("save icon (%s) is not shown", RESOURCE_ID_SAVE_ICON).that(iconView)
.isNotNull();
@@ -403,11 +473,15 @@
assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
}
- final String negativeButtonText = (negativeButtonStyle
- == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) ? "NOT NOW" : "NO THANKS";
- UiObject2 negativeButton = snackbar.findObject(By.text(negativeButtonText));
- assertWithMessage("negative button (%s)", negativeButtonText)
- .that(negativeButton).isNotNull();
+ final String negativeButtonStringId =
+ (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT)
+ ? RESOURCE_STRING_SAVE_BUTTON_NOT_NOW
+ : RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
+ final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
+ final UiObject2 negativeButton = waitForObject(snackbar,
+ By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
+ assertWithMessage("wrong text on negative button")
+ .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
final String expectedAccessibilityTitle =
getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
@@ -422,7 +496,7 @@
* @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
* @param types expected types of save info.
*/
- void saveForAutofill(boolean yesDoIt, int... types) {
+ void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
final UiObject2 saveSnackBar = assertSaveShowing(
SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
saveForAutofill(saveSnackBar, yesDoIt);
@@ -434,7 +508,7 @@
* @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
* @param types expected types of save info.
*/
- void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types) {
+ void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types) throws Exception {
final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle,null, types);
saveForAutofill(saveSnackBar, yesDoIt);
}
@@ -464,14 +538,14 @@
*
* @param id resource id of the field.
*/
- UiObject2 getAutofillMenuOption(String id) {
+ UiObject2 getAutofillMenuOption(String id) throws Exception {
final UiObject2 field = waitForObject(By.res(mPackageName, id));
// TODO: figure out why obj.longClick() doesn't always work
field.click(3000);
final List<UiObject2> menuItems = waitForObjects(
- By.res("android", RESOURCE_ID_CONTEXT_MENUITEM));
- final String expectedText = getString(RESOURCE_STRING_AUTOFILL);
+ By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
+ final String expectedText = getAutofillContextualMenuTitle();
final StringBuffer menuNames = new StringBuffer();
for (UiObject2 menuItem : menuItems) {
final String menuName = menuItem.getText();
@@ -483,6 +557,10 @@
throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
}
+ String getAutofillContextualMenuTitle() {
+ return getString(RESOURCE_STRING_AUTOFILL);
+ }
+
/**
* Gets a string from the Android resources.
*/
@@ -506,8 +584,8 @@
*
* @param selector {@link BySelector} that identifies the object.
*/
- private UiObject2 waitForObject(BySelector selector) {
- return waitForObject(selector, UI_TIMEOUT_MS);
+ private UiObject2 waitForObject(BySelector selector) throws Exception {
+ return waitForObject(selector, mDefaultTimeout);
}
/**
@@ -515,24 +593,30 @@
*
* @param parent where to find the object (or {@code null} to use device's root).
* @param selector {@link BySelector} that identifies the object.
- * @param timeout timeout in ms
+ * @param timeout timeout in ms.
+ * @param dumpOnError whether the window hierarchy should be dumped if the object is not found.
*/
- private UiObject2 waitForObject(UiObject2 parent, BySelector selector, long timeout) {
+ private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout,
+ boolean dumpOnError) throws Exception {
// NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
- final int maxTries = 5;
- final long napTime = timeout / maxTries;
- for (int i = 1; i <= maxTries; i++) {
- final UiObject2 uiObject = parent != null
- ? parent.findObject(selector)
- : mDevice.findObject(selector);
- if (uiObject != null) {
- return uiObject;
- }
- SystemClock.sleep(napTime);
- }
- throw new RetryableException("Object with selector '%s' not found in %d ms",
- selector, UI_TIMEOUT_MS);
+ try {
+ return timeout.run("waitForObject(" + selector + ")", () -> {
+ return parent != null
+ ? parent.findObject(selector)
+ : mDevice.findObject(selector);
+ });
+ } catch (RetryableException e) {
+ if (dumpOnError) {
+ dumpScreen("waitForObject() for " + selector + "failed");
+ }
+ throw e;
+ }
+ }
+
+ private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout)
+ throws Exception {
+ return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
}
/**
@@ -541,17 +625,25 @@
* @param selector {@link BySelector} that identifies the object.
* @param timeout timeout in ms
*/
- private UiObject2 waitForObject(BySelector selector, long timeout) {
+ private UiObject2 waitForObject(BySelector selector, Timeout timeout) throws Exception {
return waitForObject(null, selector, timeout);
}
/**
- * Waits for and returns a list of objects.
- *
- * @param selector {@link BySelector} that identifies the object.
+ * Execute a Runnable and wait for TYPE_WINDOWS_CHANGED or TYPE_WINDOW_STATE_CHANGED.
+ * TODO: No longer need Retry, Refactoring the Timeout (e.g. we probably need two values:
+ * one large timeout value that expects window event, one small value that expect no window
+ * event)
*/
- private List<UiObject2> waitForObjects(BySelector selector) {
- return waitForObjects(selector, UI_TIMEOUT_MS);
+ public void waitForWindowChange(Runnable runnable, long timeoutMillis) throws TimeoutException {
+ mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
+ switch (event.getEventType()) {
+ case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+ return true;
+ }
+ return false;
+ }, timeoutMillis);
}
/**
@@ -560,28 +652,26 @@
* @param selector {@link BySelector} that identifies the object.
* @param timeout timeout in ms
*/
- private List<UiObject2> waitForObjects(BySelector selector, long timeout) {
+ private List<UiObject2> waitForObjects(BySelector selector, Timeout timeout) throws Exception {
// NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
- final int maxTries = 5;
- final long napTime = timeout / maxTries;
- for (int i = 1; i <= maxTries; i++) {
- final List<UiObject2> uiObjects = mDevice.findObjects(selector);
- if (uiObjects != null && !uiObjects.isEmpty()) {
- return uiObjects;
- }
- SystemClock.sleep(napTime);
+ try {
+ return timeout.run("waitForObject(" + selector + ")", () -> {
+ final List<UiObject2> uiObjects = mDevice.findObjects(selector);
+ if (uiObjects != null && !uiObjects.isEmpty()) {
+ return uiObjects;
+ }
+ return null;
+
+ });
+
+ } catch (RetryableException e) {
+ dumpScreen("waitForObjects() for " + selector + "failed");
+ throw e;
}
- throw new RetryableException("Objects with selector '%s' not found in %d ms",
- selector, UI_TIMEOUT_MS);
}
- private UiObject2 findDatasetPicker() {
- return findDatasetPicker(UI_TIMEOUT_MS);
- }
-
- private UiObject2 findDatasetPicker(long timeout) {
- final UiObject2 picker = waitForObject(By.res("android", RESOURCE_ID_DATASET_PICKER),
- timeout);
+ private UiObject2 findDatasetPicker(Timeout timeout) throws Exception {
+ final UiObject2 picker = waitForObject(DATASET_PICKER_SELECTOR, timeout);
final String expectedTitle = getString(RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE);
assertAccessibilityTitle(picker, expectedTitle);
@@ -611,27 +701,12 @@
*
* @throws RetryableException if value didn't change.
*/
- public void setScreenOrientation(int orientation) {
+ public void setScreenOrientation(int orientation) throws Exception {
mAutoman.setRotation(orientation);
- long startTime = System.currentTimeMillis();
-
- while (System.currentTimeMillis() - startTime <= Helper.UI_SCREEN_ORIENTATION_TIMEOUT_MS) {
- final int actualValue = getScreenOrientation();
- if (actualValue == orientation) {
- return;
- }
- Log.w(TAG, "setScreenOrientation(): sleeping " + Helper.RETRY_MS
- + "ms until orientation is " + orientation
- + " (instead of " + actualValue + ")");
- try {
- Thread.sleep(Helper.RETRY_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- throw new RetryableException("Screen orientation didn't change to %d in %d ms", orientation,
- Helper.UI_SCREEN_ORIENTATION_TIMEOUT_MS);
+ UI_SCREEN_ORIENTATION_TIMEOUT.run("setScreenOrientation(" + orientation + ")", () -> {
+ return getScreenOrientation() == orientation ? Boolean.TRUE : null;
+ });
}
/**
@@ -646,7 +721,22 @@
/**
* Dumps the current view hierarchy int the output stream.
*/
- public void dumpScreen() throws IOException {
- mDevice.dumpWindowHierarchy(System.out);
+ public void dumpScreen(String cause) {
+ try {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ mDevice.dumpWindowHierarchy(os);
+ os.flush();
+ Log.w(TAG, "Dumping window hierarchy because " + cause);
+ for (String line : os.toString("UTF-8").split("\n")) {
+ Log.w(TAG, line);
+ // Sleep a little bit to avoid logs being ignored due to spam
+ SystemClock.sleep(100);
+ }
+ }
+ } catch (IOException e) {
+ // Just ignore it...
+ Log.e(TAG, "exception dumping window hierarchy", e);
+ return;
+ }
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
new file mode 100644
index 0000000..951f6e4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.autofillservice.cts.common.SettingsStateChangerRule;
+import android.content.Context;
+import android.service.autofill.UserData;
+import android.support.test.InstrumentationRegistry;
+
+import com.google.common.base.Strings;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UserDataTest {
+
+ private static final Context sContext = InstrumentationRegistry.getContext();
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+ new SettingsStateChangerRule(sContext,
+ AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+ new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "10");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMinValueChanger =
+ new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "5");
+
+ @ClassRule
+ public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+ new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+
+ private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+ private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+ + Strings.repeat("?", UserData.getMaxValueLength());
+ private final String mRemoteId = "id1";
+ private final String mRemoteId2 = "id2";
+ private final String mValue = mShortValue + "-1";
+ private final String mValue2 = mShortValue + "-2";
+
+ private UserData.Builder mBuilder;
+
+ @Before
+ public void setFixtures() {
+ mBuilder = new UserData.Builder(mRemoteId, mValue);
+ }
+
+ @Test
+ public void testBuilder_invalid() {
+ assertThrows(NullPointerException.class, () -> new UserData.Builder(mRemoteId, null));
+ assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mRemoteId, ""));
+ assertThrows(IllegalArgumentException.class,
+ () -> new UserData.Builder(mRemoteId, mShortValue));
+ assertThrows(IllegalArgumentException.class,
+ () -> new UserData.Builder(mRemoteId, mLongValue));
+ assertThrows(NullPointerException.class, () -> new UserData.Builder(null, mValue));
+ assertThrows(IllegalArgumentException.class, () -> new UserData.Builder("", mValue));
+ }
+
+ @Test
+ public void testAdd_invalid() {
+ assertThrows(NullPointerException.class, () -> mBuilder.add(mRemoteId, null));
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mRemoteId, ""));
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mRemoteId, mShortValue));
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mRemoteId, mLongValue));
+ assertThrows(NullPointerException.class, () -> mBuilder.add(null, mValue));
+ assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mValue));
+ }
+
+ @Test
+ public void testAdd_duplicatedId() {
+ assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId, mValue2));
+ }
+
+ @Test
+ public void testAdd_duplicatedValue() {
+ assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId2, mValue));
+ }
+
+ @Test
+ public void testAdd_maximumReached() {
+ // Must start from 1 because first is added on builder
+ for (int i = 1; i < UserData.getMaxFieldClassificationIdsSize() - 1; i++) {
+ mBuilder.add("ID" + i, mShortValue.toUpperCase() + i);
+ }
+ assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId, mValue));
+ }
+
+ @Test
+ public void testBuild_valid() {
+ assertThat(mBuilder.build()).isNotNull();
+ }
+
+ @Test
+ public void testNoMoreInteractionsAfterBuild() {
+ testBuild_valid();
+
+ assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId2, mValue));
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
index 21ebac2..194fba8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
@@ -21,22 +21,24 @@
import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.service.autofill.InternalValidator;
import android.service.autofill.LuhnChecksumValidator;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.Validator;
-import android.service.autofill.Validators;
-import android.support.annotation.NonNull;
+import android.service.autofill.ValueFinder;
import android.view.View;
import android.view.autofill.AutofillId;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import java.util.function.BiFunction;
-import java.util.regex.Pattern;
-
+/**
+ * Simple integration test to verify that the UI is only shown if the validator passes.
+ */
public class ValidatorTest extends AutoFillServiceTestCase {
@Rule
public final AutofillActivityTestRule<LoginActivity> mActivityRule =
@@ -49,29 +51,30 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
+ @Test
+ public void testShowUiWhenValidatorPass() throws Exception {
+ integrationTest(true);
}
- /**
- * Base test
- *
- * @param validatorBuilder method to build a validator
- * @param willSaveBeShown Whether the save pop-up will be shown
- */
- private void testValidator(
- @NonNull BiFunction<AutofillId, AutofillId, Validator> validatorBuilder,
- boolean willSaveBeShown) throws Exception {
+ @Test
+ public void testDontShowUiWhenValidatorFails() throws Exception {
+ integrationTest(false);
+ }
+
+ private void integrationTest(boolean willSaveBeShown) throws Exception {
enableService();
- AutofillId usernameId = mActivity.getUsername().getAutofillId();
- AutofillId passwordId = mActivity.getPassword().getAutofillId();
+ final AutofillId usernameId = mActivity.getUsername().getAutofillId();
+
+ final String username = willSaveBeShown ? "7992739871-3" : "4815162342-108";
+ final LuhnChecksumValidator validator = new LuhnChecksumValidator(usernameId);
+ // Sanity check to make sure the validator is properly configured
+ assertValidator(validator, usernameId, username, willSaveBeShown);
// Set response
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
- .setValidator(validatorBuilder.apply(usernameId, passwordId))
+ .setValidator(validator)
.build());
// Trigger auto-fill
@@ -81,59 +84,24 @@
sReplier.getNextFillRequest();
// Trigger save.
- mActivity.onUsername((v) -> v.setText("7992739871-3"));
- mActivity.onPassword((v) -> v.setText("passwd"));
+ mActivity.onUsername((v) -> v.setText(username));
+ mActivity.onPassword((v) -> v.setText("pass"));
mActivity.tapLogin();
if (willSaveBeShown) {
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
sReplier.getNextSaveRequest();
} else {
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
}
assertNoDanglingSessions();
}
- @Test
- public void checkForInvalidField() throws Exception {
- testValidator((usernameId, passwordId) -> Validators.or(
- new LuhnChecksumValidator(new AutofillId(-1)),
- new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
- }
-
- @Test
- public void checkBoth() throws Exception {
- testValidator((usernameId, passwordId) -> Validators.and(
- new LuhnChecksumValidator(usernameId),
- new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
- }
-
- @Test
- public void checkEither1() throws Exception {
- testValidator((usernameId, passwordId) -> Validators.or(
- new RegexValidator(usernameId, Pattern.compile("7.*")),
- new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
- }
-
- @Test
- public void checkEither2() throws Exception {
- testValidator((usernameId, passwordId) -> Validators.or(
- new RegexValidator(usernameId, Pattern.compile("invalid")),
- new RegexValidator(passwordId, Pattern.compile("pass.*"))), true);
- }
-
- @Test
- public void checkBothButFail() throws Exception {
- testValidator((usernameId, passwordId) -> Validators.and(
- new RegexValidator(usernameId, Pattern.compile("7.*")),
- new RegexValidator(passwordId, Pattern.compile("invalid"))), false);
- }
-
- @Test
- public void checkEitherButFail() throws Exception {
- testValidator((usernameId, passwordId) -> Validators.or(
- new RegexValidator(usernameId, Pattern.compile("invalid")),
- new RegexValidator(passwordId, Pattern.compile("invalid"))), false);
+ private void assertValidator(InternalValidator validator, AutofillId id, String text,
+ boolean valid) {
+ final ValueFinder valueFinder = mock(ValueFinder.class);
+ doReturn(text).when(valueFinder).findByAutofillId(id);
+ assertThat(validator.isValid(valueFinder)).isEqualTo(valid);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
new file mode 100644
index 0000000..6c7979d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts;
+
+import static android.service.autofill.Validators.and;
+import static android.service.autofill.Validators.not;
+import static android.service.autofill.Validators.or;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.service.autofill.InternalValidator;
+import android.service.autofill.Validator;
+import android.service.autofill.ValueFinder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ValidatorsTest extends AutoFillServiceTestCase {
+
+ @Mock private Validator mInvalidValidator;
+ @Mock private ValueFinder mValueFinder;
+ @Mock private InternalValidator mValidValidator;
+ @Mock private InternalValidator mValidValidator2;
+
+ @Test
+ public void testAnd_null() {
+ assertThrows(NullPointerException.class, () -> and((Validator) null));
+ assertThrows(NullPointerException.class, () -> and(mValidValidator, null));
+ assertThrows(NullPointerException.class, () -> and(null, mValidValidator));
+ }
+
+ @Test
+ public void testAnd_invalid() {
+ assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator));
+ assertThrows(IllegalArgumentException.class, () -> and(mValidValidator, mInvalidValidator));
+ assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator, mValidValidator));
+ }
+
+ @Test
+ public void testAnd_firstFailed() {
+ doReturn(false).when(mValidValidator).isValid(mValueFinder);
+ assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+ .isValid(mValueFinder)).isFalse();
+ verify(mValidValidator2, never()).isValid(mValueFinder);
+ }
+
+ @Test
+ public void testAnd_firstPassedSecondFailed() {
+ doReturn(true).when(mValidValidator).isValid(mValueFinder);
+ doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+ assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+ .isValid(mValueFinder)).isFalse();
+ }
+
+ @Test
+ public void testAnd_AllPassed() {
+ doReturn(true).when(mValidValidator).isValid(mValueFinder);
+ doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+ assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+ .isValid(mValueFinder)).isTrue();
+ }
+
+ @Test
+ public void testOr_null() {
+ assertThrows(NullPointerException.class, () -> or((Validator) null));
+ assertThrows(NullPointerException.class, () -> or(mValidValidator, null));
+ assertThrows(NullPointerException.class, () -> or(null, mValidValidator));
+ }
+
+ @Test
+ public void testOr_invalid() {
+ assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator));
+ assertThrows(IllegalArgumentException.class, () -> or(mValidValidator, mInvalidValidator));
+ assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator, mValidValidator));
+ }
+
+ @Test
+ public void testOr_AllFailed() {
+ doReturn(false).when(mValidValidator).isValid(mValueFinder);
+ doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+ assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+ .isValid(mValueFinder)).isFalse();
+ }
+
+ @Test
+ public void testOr_firstPassed() {
+ doReturn(true).when(mValidValidator).isValid(mValueFinder);
+ assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+ .isValid(mValueFinder)).isTrue();
+ verify(mValidValidator2, never()).isValid(mValueFinder);
+ }
+
+ @Test
+ public void testOr_secondPassed() {
+ doReturn(false).when(mValidValidator).isValid(mValueFinder);
+ doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+ assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+ .isValid(mValueFinder)).isTrue();
+ }
+
+ @Test
+ public void testNot_null() {
+ assertThrows(IllegalArgumentException.class, () -> not(null));
+ }
+
+ @Test
+ public void testNot_invalidClass() {
+ assertThrows(IllegalArgumentException.class, () -> not(mInvalidValidator));
+ }
+
+ @Test
+ public void testNot_falseToTrue() {
+ doReturn(false).when(mValidValidator).isValid(mValueFinder);
+ final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+ assertThat(notValidator.isValid(mValueFinder)).isTrue();
+ }
+
+ @Test
+ public void testNot_trueToFalse() {
+ doReturn(true).when(mValidValidator).isValid(mValueFinder);
+ final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+ assertThat(notValidator.isValid(mValueFinder)).isFalse();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 31eae24..75bd25e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -42,13 +42,11 @@
import android.support.test.uiautomator.UiObject2;
import android.view.autofill.AutofillManager;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Test case for an activity containing virtual children.
@@ -66,11 +64,6 @@
mActivity = mActivityRule.getActivity();
}
- @After
- public void finishWelcomeActivity() {
- WelcomeActivity.finishIt();
- }
-
@Test
public void testAutofillSync() throws Exception {
autofillTest(true);
@@ -82,6 +75,30 @@
}
/**
+ * Focus to username and expect window event
+ */
+ void focusToUsername() throws TimeoutException {
+ mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true),
+ Timeouts.UI_TIMEOUT.getMaxValue());
+ }
+
+ /**
+ * Focus to username and expect no autofill window event
+ */
+ void focusToUsernameExpectNoWindowEvent() throws Throwable {
+ // TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
+ mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
+ }
+
+ /**
+ * Focus to password and expect window event
+ */
+ void focusToPassword() throws TimeoutException {
+ mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true),
+ Timeouts.UI_TIMEOUT.getMaxValue());
+ }
+
+ /**
* Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
*/
private void autofillTest(boolean sync) throws Exception {
@@ -98,13 +115,13 @@
mActivity.mCustomView.setSync(sync);
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
assertDatasetShown(mActivity.mUsername, "The Dude");
// Play around with focus to make sure picker is properly drawn.
- mActivity.mPassword.changeFocus(true);
+ focusToPassword();
assertDatasetShown(mActivity.mPassword, "The Dude");
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
assertDatasetShown(mActivity.mUsername, "The Dude");
// Make sure input was sanitized.
@@ -145,7 +162,7 @@
assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -176,18 +193,18 @@
mActivity.expectAutoFill("DUDE", "SWEET");
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
// Play around with focus to make sure picker is properly drawn.
- mActivity.mPassword.changeFocus(true);
+ focusToPassword();
assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
// Auto-fill it.
- sUiBot.selectDataset("THE DUDE");
+ mUiBot.selectDataset("THE DUDE");
// Check the results.
mActivity.assertAutoFilled();
@@ -217,7 +234,7 @@
sReplier.getNextFillRequest();
// Select datatest.
- sUiBot.selectDataset("The Dude");
+ mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -267,8 +284,8 @@
sReplier.getNextFillRequest();
// Auto-fill it.
- final UiObject2 picker = sUiBot.assertDatasets("The Dude", "Jenny");
- sUiBot.selectDataset(picker, pickFirst? "The Dude" : "Jenny");
+ final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
+ mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
// Check the results.
mActivity.assertAutoFilled();
@@ -293,43 +310,43 @@
mActivity.expectAutoFill("dude", "sweet");
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
sReplier.getNextFillRequest();
callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
// Change focus
- mActivity.mPassword.changeFocus(true);
+ focusToPassword();
callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
}
@Test
- public void testAutofillCallbackDisabled() throws Exception {
+ public void testAutofillCallbackDisabled() throws Throwable {
// Set service.
disableService();
final MyAutofillCallback callback = mActivity.registerCallback();
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsernameExpectNoWindowEvent();
// Assert callback was called
callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
}
@Test
- public void testAutofillCallbackNoDatasets() throws Exception {
+ public void testAutofillCallbackNoDatasets() throws Throwable {
callbackUnavailableTest(NO_RESPONSE);
}
@Test
- public void testAutofillCallbackNoDatasetsButSaveInfo() throws Exception {
+ public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
callbackUnavailableTest(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
}
- private void callbackUnavailableTest(CannedFillResponse response) throws Exception {
+ private void callbackUnavailableTest(CannedFillResponse response) throws Throwable {
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
@@ -338,11 +355,11 @@
sReplier.addResponse(response);
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Auto-fill it.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Assert callback was called
callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
@@ -365,34 +382,28 @@
mActivity.expectAutoFill("dude", "sweet");
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude");
- sUiBot.pressBack();
+ mUiBot.pressBack();
- sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
- public void testSaveDialogShownWhenAllVirtualViewsNotVisible() throws Exception {
+ public void testSaveDialogShownWhenAllVirtualViewsNotVisible() throws Throwable {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
- .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
.build());
- final CountDownLatch latch = new CountDownLatch(1);
-
// Trigger auto-fill.
- mActivity.runOnUiThread(() -> {
- mActivity.mUsername.changeFocus(true);
- latch.countDown();
- });
- latch.await(Helper.UI_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// TODO: 63602573 Should be removed once this bug is fixed
@@ -409,7 +420,7 @@
});
// Make sure save is shown
- sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
@@ -427,7 +438,7 @@
.build());
// Trigger auto-fill.
- mActivity.mUsername.changeFocus(true);
+ focusToUsername();
assertDatasetShown(mActivity.mUsername, "The Dude");
// Make sure package name was sanitized.
@@ -439,8 +450,8 @@
/**
* Asserts the dataset picker is properly displayed in a give line.
*/
- private void assertDatasetShown(Line line, String... expectedDatasets) {
- final Rect pickerBounds = sUiBot.assertDatasets(expectedDatasets).getVisibleBounds();
+ private void assertDatasetShown(Line line, String... expectedDatasets) throws Exception {
+ final Rect pickerBounds = mUiBot.assertDatasets(expectedDatasets).getVisibleBounds();
final Rect fieldBounds = line.getAbsCoordinates();
assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
index 8eecc29..92fc7a7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
@@ -16,7 +16,7 @@
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -74,7 +74,7 @@
private int mUnfocusedColor;
private boolean mSync = true;
private boolean mOverrideDispatchProvideAutofillStructure = false;
- private ComponentName mFakedComponentName;
+ private ComponentName mFackedComponentName;
public VirtualContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -195,15 +195,15 @@
Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
super.onProvideAutofillVirtualStructure(structure, flags);
- if (mFakedComponentName != null) {
- Log.d(TAG, "Faking package name to " + mFakedComponentName);
+ if (mFackedComponentName != null) {
+ Log.d(TAG, "Faking package name to " + mFackedComponentName);
try {
final AssistStructure assistStructure = Helper.getField(structure, "mAssist");
if (assistStructure != null) {
- Helper.setField(assistStructure, "mActivityComponent", mFakedComponentName);
+ Helper.setField(assistStructure, "mActivityComponent", mFackedComponentName);
}
} catch (Exception e) {
- Log.e(TAG, "Could not fake package name to " + mFakedComponentName, e);
+ Log.e(TAG, "Could not fake package name to " + mFackedComponentName, e);
}
}
@@ -270,9 +270,10 @@
}
void fakePackageName(ComponentName name) {
- mFakedComponentName = name;
+ mFackedComponentName = name;
}
+
void setOverrideDispatchProvideAutofillStructure(boolean flag) {
mOverrideDispatchProvideAutofillStructure = flag;
}
@@ -372,8 +373,8 @@
}
void assertAutoFilled() throws Exception {
- final boolean set = latch.await(FILL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT_MS, label)
+ final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+ assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT.ms(), label)
.that(set).isTrue();
final String actual = text.text.toString();
assertWithMessage("Wrong auto-fill value on Line %s", label)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
index 9bc3df0..a7c880f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
@@ -18,11 +18,13 @@
import android.os.Bundle;
import android.support.test.uiautomator.UiObject2;
import android.util.Log;
+import android.view.View;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
+import android.widget.LinearLayout;
import java.io.IOException;
@@ -33,7 +35,18 @@
private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
static final String ID_WEBVIEW = "webview";
- WebView mWebView;
+ static final String HTML_NAME_USERNAME = "username";
+ static final String HTML_NAME_PASSWORD = "password";
+
+ static final String ID_OUTSIDE1 = "outside1";
+ static final String ID_OUTSIDE2 = "outside2";
+
+ private MyWebView mWebView;
+
+ private LinearLayout mOutsideContainer1;
+ private LinearLayout mOutsideContainer2;
+ EditText mOutside1;
+ EditText mOutside2;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -64,36 +77,50 @@
}
}
});
- mWebView.loadUrl(FAKE_URL);
+
+ mOutsideContainer1 = findViewById(R.id.outsideContainer1);
+ mOutsideContainer2 = findViewById(R.id.outsideContainer2);
+ mOutside1 = findViewById(R.id.outside1);
+ mOutside2 = findViewById(R.id.outside2);
}
- public UiObject2 getUsernameLabel(UiBot uiBot) {
+ public MyWebView loadWebView() {
+ syncRunOnUiThread(() -> mWebView.loadUrl(FAKE_URL));
+ return mWebView;
+ }
+
+ public void loadOutsideViews() {
+ syncRunOnUiThread(() -> {
+ mOutsideContainer1.setVisibility(View.VISIBLE);
+ mOutsideContainer2.setVisibility(View.VISIBLE);
+ });
+ }
+
+ public UiObject2 getUsernameLabel(UiBot uiBot) throws Exception {
return getLabel(uiBot, "Username: ");
}
- public UiObject2 getPasswordLabel(UiBot uiBot) {
+ public UiObject2 getPasswordLabel(UiBot uiBot) throws Exception {
return getLabel(uiBot, "Password: ");
}
-
- public UiObject2 getUsernameInput(UiBot uiBot) {
+ public UiObject2 getUsernameInput(UiBot uiBot) throws Exception {
return getInput(uiBot, "Username: ");
}
- public UiObject2 getPasswordInput(UiBot uiBot) {
+ public UiObject2 getPasswordInput(UiBot uiBot) throws Exception {
return getInput(uiBot, "Password: ");
}
- public UiObject2 getLoginButton(UiBot uiBot) {
- return uiBot.assertShownByContentDescription("Login");
+ public UiObject2 getLoginButton(UiBot uiBot) throws Exception {
+ return getLabel(uiBot, "Login");
}
- private UiObject2 getLabel(UiBot uiBot, String contentDescription) {
- final UiObject2 label = uiBot.assertShownByContentDescription(contentDescription);
- return label;
+ private UiObject2 getLabel(UiBot uiBot, String label) throws Exception {
+ return uiBot.assertShownByText(label);
}
- private UiObject2 getInput(UiBot uiBot, String contentDescription) {
+ private UiObject2 getInput(UiBot uiBot, String contentDescription) throws Exception {
// First get the label..
final UiObject2 label = getLabel(uiBot, contentDescription);
@@ -105,10 +132,12 @@
if (child.getClassName().equals(EditText.class.getName())) {
return child;
}
+ uiBot.dumpScreen("getInput() for " + child + "failed");
throw new IllegalStateException("Invalid class for " + child);
}
previous = child;
}
+ uiBot.dumpScreen("getInput() for label " + label + "failed");
throw new IllegalStateException("could not find username (label=" + label + ")");
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
index 7eb73d2..c6a2d4c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
@@ -15,7 +15,11 @@
*/
package android.autofillservice.cts;
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE1;
+import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE2;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
@@ -65,15 +69,18 @@
// Set service.
enableService();
+ // Load WebView
+ mActivity.loadWebView();
+
// Set expectations.
sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
// Trigger autofill.
- mActivity.getUsernameInput(sUiBot).click();
+ mActivity.getUsernameInput(mUiBot).click();
sReplier.getNextFillRequest();
// Assert not shown.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
}
@Test
@@ -81,57 +88,56 @@
// Set service.
enableService();
+ // Load WebView
+ final MyWebView myWebView = mActivity.loadWebView();
+
// Set expectations.
+ myWebView.expectAutofill("dude", "sweet");
final MyAutofillCallback callback = mActivity.registerCallback();
sReplier.addResponse(new CannedDataset.Builder()
- .setField("username", "dude")
- .setField("password", "sweet")
+ .setField(HTML_NAME_USERNAME, "dude")
+ .setField(HTML_NAME_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build());
// Trigger autofill.
- mActivity.getUsernameInput(sUiBot).click();
+ mActivity.getUsernameInput(mUiBot).click();
final FillRequest fillRequest = sReplier.getNextFillRequest();
- sUiBot.assertDatasets("The Dude");
+ mUiBot.assertDatasets("The Dude");
// Change focus around.
- final int usernameChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
- mActivity.getUsernameLabel(sUiBot).click();
- callback.assertUiHiddenEvent(mActivity.mWebView, usernameChildId);
- sUiBot.assertNoDatasets();
- mActivity.getPasswordInput(sUiBot).click();
- final int passwordChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
- final UiObject2 datasetPicker = sUiBot.assertDatasets("The Dude");
+ final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+ mActivity.getUsernameLabel(mUiBot).click();
+ callback.assertUiHiddenEvent(myWebView, usernameChildId);
+ mUiBot.assertNoDatasets();
+ mActivity.getPasswordInput(mUiBot).click();
+ final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
// Now Autofill it.
- sUiBot.selectDataset(datasetPicker, "The Dude");
- sUiBot.assertNoDatasets();
- callback.assertUiHiddenEvent(mActivity.mWebView, passwordChildId);
+ mUiBot.selectDataset(datasetPicker, "The Dude");
+ myWebView.assertAutofilled();
+ mUiBot.assertNoDatasets();
+ callback.assertUiHiddenEvent(myWebView, passwordChildId);
// Make sure screen was autofilled.
- assertThat(mActivity.getUsernameInput(sUiBot).getText()).isEqualTo("dude");
+ assertThat(mActivity.getUsernameInput(mUiBot).getText()).isEqualTo("dude");
// TODO: proper way to verify text (which is ..... because it's a password) - ideally it
// should call passwordInput.isPassword(), but that's not exposed
- final String password = mActivity.getPasswordInput(sUiBot).getText();
+ final String password = mActivity.getPasswordInput(mUiBot).getText();
assertThat(password).isNotEqualTo("sweet");
assertThat(password).hasLength(5);
// Assert structure passed to service.
try {
- final ViewNode webViewNode = Helper.findWebViewNode(fillRequest.structure, "FORM AM I");
- // TODO(b/66953802): class name should be android.webkit.WebView, and form name should
- // be inside HtmlInfo, but Chromium 61 does not implement that.
- if (webViewNode.getClassName().equals("android.webkit.WebView")) {
- final HtmlInfo htmlInfo = Helper.assertHasHtmlTag(webViewNode, "form");
- Helper.assertHasAttribute(htmlInfo, "name", "FORM AM I");
- } else {
- assertThat(webViewNode.getClassName()).isEqualTo("FORM AM I");
- assertThat(webViewNode.getHtmlInfo()).isNull();
- }
+ final ViewNode webViewNode =
+ Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
+ assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
+ assertThat(webViewNode.getWebScheme()).isEqualTo("https");
final ViewNode usernameNode =
- Helper.findNodeByHtmlName(fillRequest.structure, "username");
+ Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
Helper.assertTextIsSanitized(usernameNode);
final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
@@ -141,7 +147,7 @@
assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
final ViewNode passwordNode =
- Helper.findNodeByHtmlName(fillRequest.structure, "password");
+ Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
Helper.assertTextIsSanitized(passwordNode);
final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
@@ -161,37 +167,43 @@
// Set service.
enableService();
+ // Load WebView
+ mActivity.loadWebView();
+
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
- .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, "username", "password")
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+ HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
.build());
// Trigger autofill.
- mActivity.getUsernameInput(sUiBot).click();
+ mActivity.getUsernameInput(mUiBot).click();
sReplier.getNextFillRequest();
// Assert not shown.
- sUiBot.assertNoDatasets();
+ mUiBot.assertNoDatasets();
// Trigger save.
if (INJECT_EVENTS) {
- mActivity.getUsernameInput(sUiBot).click();
+ mActivity.getUsernameInput(mUiBot).click();
runShellCommand("input keyevent KEYCODE_U");
- mActivity.getPasswordInput(sUiBot).click();
+ mActivity.getPasswordInput(mUiBot).click();
runShellCommand("input keyevent KEYCODE_P");
} else {
- mActivity.getUsernameInput(sUiBot).setText("DUDE");
- mActivity.getPasswordInput(sUiBot).setText("SWEET");
+ mActivity.getUsernameInput(mUiBot).setText("DUDE");
+ mActivity.getPasswordInput(mUiBot).setText("SWEET");
}
- mActivity.getLoginButton(sUiBot).click();
+ mActivity.getLoginButton(mUiBot).click();
// Assert save UI shown.
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
// Assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
- final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure, "username");
- final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure, "password");
+ final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_USERNAME);
+ final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_PASSWORD);
if (INJECT_EVENTS) {
Helper.assertTextAndValue(usernameNode, "u");
Helper.assertTextAndValue(passwordNode, "p");
@@ -206,64 +218,74 @@
// Set service.
enableService();
+ // Load WebView
+ final MyWebView myWebView = mActivity.loadWebView();
+
// Set expectations.
final MyAutofillCallback callback = mActivity.registerCallback();
+ myWebView.expectAutofill("dude", "sweet");
sReplier.addResponse(new CannedFillResponse.Builder()
- .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, "username", "password")
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+ HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
.addDataset(new CannedDataset.Builder()
- .setField("username", "dude")
- .setField("password", "sweet")
+ .setField(HTML_NAME_USERNAME, "dude")
+ .setField(HTML_NAME_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build())
.build());
// Trigger autofill.
- mActivity.getUsernameInput(sUiBot).click();
+ mActivity.getUsernameInput(mUiBot).click();
final FillRequest fillRequest = sReplier.getNextFillRequest();
- sUiBot.assertDatasets("The Dude");
- final int usernameChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
+ mUiBot.assertDatasets("The Dude");
+ final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
// Assert structure passed to service.
- final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure, "username");
+ final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
+ HTML_NAME_USERNAME);
Helper.assertTextIsSanitized(usernameNode);
assertThat(usernameNode.isFocused()).isTrue();
assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
- final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure, "password");
+ final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
+ HTML_NAME_PASSWORD);
Helper.assertTextIsSanitized(passwordNode);
assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
assertThat(passwordNode.isFocused()).isFalse();
// Autofill it.
- sUiBot.selectDataset("The Dude");
- callback.assertUiHiddenEvent(mActivity.mWebView, usernameChildId);
+ mUiBot.selectDataset("The Dude");
+ myWebView.assertAutofilled();
+ callback.assertUiHiddenEvent(myWebView, usernameChildId);
// Make sure screen was autofilled.
- assertThat(mActivity.getUsernameInput(sUiBot).getText()).isEqualTo("dude");
+ assertThat(mActivity.getUsernameInput(mUiBot).getText()).isEqualTo("dude");
// TODO: proper way to verify text (which is ..... because it's a password) - ideally it
// should call passwordInput.isPassword(), but that's not exposed
- final String password = mActivity.getPasswordInput(sUiBot).getText();
+ final String password = mActivity.getPasswordInput(mUiBot).getText();
assertThat(password).isNotEqualTo("sweet");
assertThat(password).hasLength(5);
// Now trigger save.
if (INJECT_EVENTS) {
- mActivity.getUsernameInput(sUiBot).click();
+ mActivity.getUsernameInput(mUiBot).click();
runShellCommand("input keyevent KEYCODE_U");
- mActivity.getPasswordInput(sUiBot).click();
+ mActivity.getPasswordInput(mUiBot).click();
runShellCommand("input keyevent KEYCODE_P");
} else {
- mActivity.getUsernameInput(sUiBot).setText("DUDE");
- mActivity.getPasswordInput(sUiBot).setText("SWEET");
+ mActivity.getUsernameInput(mUiBot).setText("DUDE");
+ mActivity.getPasswordInput(mUiBot).setText("SWEET");
}
- mActivity.getLoginButton(sUiBot).click();
+ mActivity.getLoginButton(mUiBot).click();
// Assert save UI shown.
- sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
// Assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
- final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure, "username");
- final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure, "password");
+ final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_USERNAME);
+ final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_PASSWORD);
if (INJECT_EVENTS) {
Helper.assertTextAndValue(usernameNode2, "dudeu");
Helper.assertTextAndValue(passwordNode2, "sweetp");
@@ -272,4 +294,285 @@
Helper.assertTextAndValue(passwordNode2, "SWEET");
}
}
+
+ @Test
+ public void testAutofillAndSave_withExternalViews_loadWebViewFirst() throws Exception {
+ // Set service.
+ enableService();
+
+ // Load views
+ final MyWebView myWebView = mActivity.loadWebView();
+ mActivity.loadOutsideViews();
+
+ // Set expectations.
+ myWebView.expectAutofill("dude", "sweet");
+ final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+ mActivity.mOutside1, "duder");
+ final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+ mActivity.mOutside2, "sweeter");
+ mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+ mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+ HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+ .addDataset(new CannedDataset.Builder()
+ .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+ .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+ .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+ .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.getUsernameInput(mUiBot).click();
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("USER");
+ final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+ // Assert structure passed to service.
+ final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+ HTML_NAME_USERNAME);
+ Helper.assertTextIsSanitized(usernameFillNode);
+ assertThat(usernameFillNode.isFocused()).isTrue();
+ assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+ final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+ HTML_NAME_PASSWORD);
+ Helper.assertTextIsSanitized(passwordFillNode);
+ assertThat(passwordFillNode.getAutofillHints()).asList()
+ .containsExactly("current-password");
+ assertThat(passwordFillNode.isFocused()).isFalse();
+
+ final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+ ID_OUTSIDE1);
+ Helper.assertTextIsSanitized(outside1FillNode);
+ final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+ ID_OUTSIDE2);
+ Helper.assertTextIsSanitized(outside2FillNode);
+
+ // Move focus around to make sure UI is shown accordingly
+ mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+ callback.assertUiHiddenEvent(myWebView, usernameChildId);
+ mUiBot.assertDatasets("OUT1");
+ callback.assertUiShownEvent(mActivity.mOutside1);
+
+ mActivity.getPasswordInput(mUiBot).click();
+ callback.assertUiHiddenEvent(mActivity.mOutside1);
+ mUiBot.assertDatasets("PASS");
+ final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(myWebView, passwordChildId);
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+ callback.assertUiShownEvent(mActivity.mOutside2);
+
+ // Autofill it.
+ mUiBot.selectDataset(datasetPicker, "OUT2");
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+
+ myWebView.assertAutofilled();
+ outside1Watcher.assertAutoFilled();
+ outside2Watcher.assertAutoFilled();
+
+ // Now trigger save.
+ if (INJECT_EVENTS) {
+ mActivity.getUsernameInput(mUiBot).click();
+ runShellCommand("input keyevent KEYCODE_U");
+ mActivity.getPasswordInput(mUiBot).click();
+ runShellCommand("input keyevent KEYCODE_P");
+ } else {
+ mActivity.getUsernameInput(mUiBot).setText("DUDE");
+ mActivity.getPasswordInput(mUiBot).setText("SWEET");
+ }
+ mActivity.runOnUiThread(() -> {
+ mActivity.mOutside1.setText("DUDER");
+ mActivity.mOutside2.setText("SWEETER");
+ });
+
+ mActivity.getLoginButton(mUiBot).click();
+
+ // Assert save UI shown.
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+ // Assert results
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_USERNAME);
+ final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_PASSWORD);
+ if (INJECT_EVENTS) {
+ Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+ Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+ } else {
+ Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+ Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+ }
+
+ final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+ ID_OUTSIDE1);
+ Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+ final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+ ID_OUTSIDE2);
+ Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+ }
+
+
+ @Test
+ public void testAutofillAndSave_withExternalViews_loadExternalViewsFirst() throws Exception {
+ if (true) return; // TODO(b/69461853): re-enable once WebView fixes it
+
+ // Set service.
+ enableService();
+
+ // Load outside views
+ mActivity.loadOutsideViews();
+
+ // Set expectations.
+ final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+ mActivity.mOutside1, "duder");
+ final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+ mActivity.mOutside2, "sweeter");
+ mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+ mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+ final MyAutofillCallback callback = mActivity.registerCallback();
+ sReplier.setIdMode(IdMode.RESOURCE_ID);
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+ .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+ final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+ mUiBot.assertDatasets("OUT1");
+ callback.assertUiShownEvent(mActivity.mOutside1);
+
+ // Move focus around to make sure UI is shown accordingly
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(mActivity.mOutside1);
+ mUiBot.assertDatasets("OUT2");
+ callback.assertUiShownEvent(mActivity.mOutside2);
+
+ // Assert structure passed to service.
+ final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+ ID_OUTSIDE1);
+ Helper.assertTextIsSanitized(outside1FillNode);
+ final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+ ID_OUTSIDE2);
+ Helper.assertTextIsSanitized(outside2FillNode);
+
+ // Now load Webiew
+ final MyWebView myWebView = mActivity.loadWebView();
+
+ // Set expectations
+ myWebView.expectAutofill("dude", "sweet");
+ sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+ sReplier.addResponse(new CannedFillResponse.Builder()
+ .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+ HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+ .addDataset(new CannedDataset.Builder()
+ .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+ .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+ .build())
+ .build());
+
+ // Trigger autofill.
+ mActivity.getUsernameInput(mUiBot).click();
+ final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+ mUiBot.assertDatasets("USER");
+ final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+ // Move focus around to make sure UI is shown accordingly
+ mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+ callback.assertUiHiddenEvent(myWebView, usernameChildId);
+ mUiBot.assertDatasets("OUT1");
+ callback.assertUiShownEvent(mActivity.mOutside1);
+
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(mActivity.mOutside1);
+ mUiBot.assertDatasets("OUT2");
+ callback.assertUiShownEvent(mActivity.mOutside2);
+
+ mActivity.getPasswordInput(mUiBot).click();
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+ mUiBot.assertDatasets("PASS");
+ final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(myWebView, passwordChildId);
+ final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+ callback.assertUiShownEvent(mActivity.mOutside2);
+
+ // Assert structure passed to service.
+ final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+ HTML_NAME_USERNAME);
+ Helper.assertTextIsSanitized(usernameFillNode);
+ assertThat(usernameFillNode.isFocused()).isTrue();
+ assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+ final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+ HTML_NAME_PASSWORD);
+ Helper.assertTextIsSanitized(passwordFillNode);
+ assertThat(passwordFillNode.getAutofillHints()).asList()
+ .containsExactly("current-password");
+ assertThat(passwordFillNode.isFocused()).isFalse();
+
+ // Autofill external views (2nd partition)
+ mUiBot.selectDataset(datasetPicker, "OUT2");
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+ outside1Watcher.assertAutoFilled();
+ outside2Watcher.assertAutoFilled();
+
+ // Autofill Webview (1st partition)
+ mActivity.getUsernameInput(mUiBot).click();
+ callback.assertUiShownEventForVirtualChild(myWebView);
+ mUiBot.selectDataset("USER");
+ myWebView.assertAutofilled();
+
+ // Now trigger save.
+ if (INJECT_EVENTS) {
+ mActivity.getUsernameInput(mUiBot).click();
+ runShellCommand("input keyevent KEYCODE_U");
+ mActivity.getPasswordInput(mUiBot).click();
+ runShellCommand("input keyevent KEYCODE_P");
+ } else {
+ mActivity.getUsernameInput(mUiBot).setText("DUDE");
+ mActivity.getPasswordInput(mUiBot).setText("SWEET");
+ }
+ mActivity.runOnUiThread(() -> {
+ mActivity.mOutside1.setText("DUDER");
+ mActivity.mOutside2.setText("SWEETER");
+ });
+
+ mActivity.getLoginButton(mUiBot).click();
+
+ // Assert save UI shown.
+ mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+ // Assert results
+ final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+ final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_USERNAME);
+ final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+ HTML_NAME_PASSWORD);
+ if (INJECT_EVENTS) {
+ Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+ Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+ } else {
+ Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+ Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+ }
+
+ final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+ ID_OUTSIDE1);
+ Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+ final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+ ID_OUTSIDE2);
+ Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
index 5d3baca..993951d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
@@ -17,7 +17,10 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.test.uiautomator.UiObject2;
@@ -35,9 +38,12 @@
private static final String TAG = "WelcomeActivity";
static final String EXTRA_MESSAGE = "message";
- static final String ID_OUTPUT = "output";
+ static final String ID_WELCOME = "welcome";
- private TextView mOutput;
+ private static int sPendingIntentId;
+ private static PendingIntent sPendingIntent;
+
+ private TextView mWelcome;
public WelcomeActivity() {
sInstance = this;
@@ -49,22 +55,34 @@
setContentView(R.layout.welcome_activity);
- mOutput = (TextView) findViewById(R.id.output);
+ mWelcome = (TextView) findViewById(R.id.welcome);
final Intent intent = getIntent();
final String message = intent.getStringExtra(EXTRA_MESSAGE);
if (!TextUtils.isEmpty(message)) {
- mOutput.setText(message);
+ mWelcome.setText(message);
}
- Log.d(TAG, "Output: " + mOutput.getText());
+ Log.d(TAG, "Message: " + message);
}
- static void finishIt() {
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ Log.v(TAG, "Setting sInstance to null onDestroy()");
+ sInstance = null;
+ }
+
+ static void finishIt(UiBot uiBot) {
if (sInstance != null) {
Log.d(TAG, "So long and thanks for all the fish!");
sInstance.finish();
+ uiBot.assertGoneByRelativeId(ID_WELCOME, Timeouts.ACTIVITY_RESURRECTION);
+ }
+ if (sPendingIntent != null) {
+ sPendingIntent.cancel();
}
}
@@ -75,11 +93,25 @@
// TODO: reuse in other places
static void assertShowing(UiBot uiBot, @Nullable String expectedMessage) throws Exception {
- final UiObject2 activity = uiBot.assertShownByRelativeId(ID_OUTPUT);
+ final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
if (expectedMessage == null) {
expectedMessage = "Welcome to the jungle!";
}
assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
.isEqualTo(expectedMessage);
}
+
+ public static IntentSender createSender(Context context, String message) {
+ if (sPendingIntent != null) {
+ throw new IllegalArgumentException("Already have pending intent (id="
+ + sPendingIntentId + "): " + sPendingIntent);
+ }
+ ++sPendingIntentId;
+ Log.v(TAG, "createSender: id=" + sPendingIntentId + " message=" + message);
+ final Intent intent = new Intent(context, WelcomeActivity.class)
+ .putExtra(EXTRA_MESSAGE, message)
+ .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+ sPendingIntent = PendingIntent.getActivity(context, sPendingIntentId, intent, 0);
+ return sPendingIntent.getIntentSender();
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java b/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
new file mode 100644
index 0000000..cbdcf44
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/OneTimeSettingsListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper used to block tests until a secure settings value has been updated.
+ */
+public final class OneTimeSettingsListener extends ContentObserver {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final ContentResolver mResolver;
+ private final String mKey;
+
+ public OneTimeSettingsListener(Context context, String key) {
+ super(new Handler(Looper.getMainLooper()));
+ mKey = key;
+ mResolver = context.getContentResolver();
+ mResolver.registerContentObserver(Settings.Secure.getUriFor(key), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mResolver.unregisterContentObserver(this);
+ mLatch.countDown();
+ }
+
+ /**
+ * Blocks for a few seconds until it's called.
+ *
+ * @throws IllegalStateException if it's not called.
+ */
+ public void assertCalled() {
+ try {
+ final boolean updated = mLatch.await(5, TimeUnit.SECONDS);
+ if (!updated) {
+ throw new IllegalStateException("Settings " + mKey + " not called in 5s");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted", e);
+ }
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/README.txt b/tests/autofillservice/src/android/autofillservice/cts/common/README.txt
new file mode 100644
index 0000000..97d3b48
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/README.txt
@@ -0,0 +1,2 @@
+This package contains utilities that are not tied to Autofill and might eventually move to
+a common CTS package.
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
new file mode 100644
index 0000000..374710f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * Provides utilities to interact with the device's {@link Settings}.
+ */
+// TODO: make it more generic (it's hardcoded to 'secure' provider on current user
+public final class SettingsHelper {
+
+ /**
+ * Uses a Shell command to set the given preference.
+ */
+ public static void set(@NonNull String key, @Nullable String value) {
+ if (value == null) {
+ delete(key);
+ return;
+ }
+ runShellCommand("settings put secure %s %s default", key, value);
+ }
+
+ /**
+ * Uses a Shell command to set the given preference, and verifies it was correctly set.
+ */
+ public static void syncSet(@NonNull Context context, @NonNull String key,
+ @Nullable String value) {
+ if (value == null) {
+ syncDelete(context, key);
+ return;
+ }
+
+ final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, key);
+ set(key, value);
+ observer.assertCalled();
+
+ final String newValue = get(key);
+ assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo(value);
+ }
+
+ /**
+ * Uses a Shell command to delete the given preference.
+ */
+ public static void delete(@NonNull String key) {
+ runShellCommand("settings delete secure %s", key);
+ }
+
+ /**
+ * Uses a Shell command to delete the given preference, and verifies it was correctly deleted.
+ */
+ public static void syncDelete(@NonNull Context context, @NonNull String key) {
+
+ final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, key);
+ delete(key);
+ observer.assertCalled();
+
+ final String newValue = get(key);
+ assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo("null");
+ }
+
+ /**
+ * Gets the value of a given preference using Shell command.
+ */
+ @NonNull
+ public static String get(@NonNull String key) {
+ return runShellCommand("settings get secure %s", key);
+ }
+
+ private SettingsHelper() {
+ throw new UnsupportedOperationException("contain static methods only");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.java
new file mode 100644
index 0000000..82b2ef5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.common;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * JUnit rule used to set a {@link Settings} preference before the test is run.
+ *
+ * <p>It stores the current value before the test, changes it (if necessary), then restores it after
+ * the test (if necessary).
+ */
+public class SettingsStateChangerRule extends StateChangerRule<String> {
+
+ /**
+ * Default constructor.
+ *
+ * @param context context used to retrieve the {@link Settings} provider.
+ * @param key prefence key.
+ * @param value value to be set before the test is run.
+ */
+ public SettingsStateChangerRule(@NonNull Context context, @NonNull String key,
+ @Nullable String value) {
+ super(new SettingsStateManager(context, key), value);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateKeeperRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateKeeperRule.java
new file mode 100644
index 0000000..2e8262c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateKeeperRule.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.common;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+
+/**
+ * JUnit rule used to restore a {@link Settings} preference after the test is run.
+ *
+ * <p>It stores the current value before the test, and restores it after the test (if necessary).
+ */
+public class SettingsStateKeeperRule extends StateKeeperRule<String> {
+
+ /**
+ * Default constructor.
+ *
+ * @param context context used to retrieve the {@link Settings} provider.
+ * @param key prefence key.
+ */
+ public SettingsStateKeeperRule(@NonNull Context context, @NonNull String key) {
+ super(new SettingsStateManager(context, key));
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.java b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.java
new file mode 100644
index 0000000..057c3b0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Manages the state of a preference backed by {@link Settings}.
+ */
+public class SettingsStateManager implements StateManager<String> {
+
+ private final Context mContext;
+ private final String mKey;
+
+ /**
+ * Default constructor.
+ *
+ * @param context context used to retrieve the {@link Settings} provider.
+ * @param key prefence key.
+ */
+ public SettingsStateManager(@NonNull Context context, @NonNull String key) {
+ mContext = Preconditions.checkNotNull(context);
+ mKey = Preconditions.checkNotNull(key);
+ }
+
+ @Override
+ public void set(@Nullable String value) {
+ SettingsHelper.syncSet(mContext, mKey, value);
+ }
+
+ @Override
+ @Nullable
+ public String get() {
+ return SettingsHelper.get(mKey);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
new file mode 100644
index 0000000..3e15af2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Provides Shell-based utilities such as running a command.
+ */
+public final class ShellHelper {
+
+ private static final String TAG = "ShellHelper";
+
+ /**
+ * Runs a Shell command, returning a trimmed response.
+ */
+ @NonNull
+ public static String runShellCommand(@NonNull String template, Object...args) {
+ final String command = String.format(template, args);
+ Log.d(TAG, "runShellCommand(): " + command);
+ try {
+ final String result = SystemUtil
+ .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ return TextUtils.isEmpty(result) ? "" : result.trim();
+ } catch (Exception e) {
+ throw new RuntimeException("Command '" + command + "' failed: ", e);
+ }
+ }
+
+ private ShellHelper() {
+ throw new UnsupportedOperationException("contain static methods only");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRule.java
new file mode 100644
index 0000000..8e94e2d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRule.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.Objects;
+
+/**
+ * JUnit rule used to prepare a state before the test is run.
+ *
+ * <p>It stores the current state before the test, changes it (if necessary), then restores it after
+ * the test (if necessary).
+ */
+public class StateChangerRule<T> implements TestRule {
+
+ private final StateManager<T> mStateManager;
+ private final T mValue;
+
+ /**
+ * Default constructor.
+ *
+ * @param stateManager abstraction used to mange the state.
+ * @param value value to be set before the test is run.
+ */
+ public StateChangerRule(@NonNull StateManager<T> stateManager, @Nullable T value) {
+ mStateManager = Preconditions.checkNotNull(stateManager);
+ mValue = value;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ final T previousValue = mStateManager.get();
+ if (!Objects.equals(previousValue, mValue)) {
+ mStateManager.set(mValue);
+ }
+ try {
+ base.evaluate();
+ } finally {
+ final T currentValue = mStateManager.get();
+ if (!Objects.equals(currentValue, previousValue)) {
+ mStateManager.set(previousValue);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRuleTest.java
new file mode 100644
index 0000000..9c2018e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateChangerRuleTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StateChangerRuleTest {
+
+ private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
+ private final Description mDescription = Description.createSuiteDescription("Whatever");
+
+ @Mock
+ private StateManager<String> mStateManager;
+
+ @Mock
+ private Statement mStatement;
+
+ @Test
+ public void testInvalidConstructor() {
+ assertThrows(NullPointerException.class,
+ () -> new StateChangerRule<Object>(null, "value"));
+ }
+
+ @Test
+ public void testSetAndRestoreOnSuccess() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "newValue");
+ when(mStateManager.get()).thenReturn("before", "changed");
+
+ rule.apply(mStatement, mDescription).evaluate();
+
+ verify(mStatement, times(1)).evaluate();
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("newValue");
+ verify(mStateManager, times(1)).set("before");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testDontSetIfSameValueOnSuccess() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "sameValue");
+ when(mStateManager.get()).thenReturn("sameValue");
+
+ rule.apply(mStatement, mDescription).evaluate();
+
+ verify(mStatement, times(1)).evaluate();
+ verify(mStateManager, never()).set(anyString());
+ }
+
+ @Test
+ public void testSetButDontRestoreIfSameValueOnSuccess() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "newValue");
+ when(mStateManager.get()).thenReturn("before", "before");
+
+ rule.apply(mStatement, mDescription).evaluate();
+
+ verify(mStatement, times(1)).evaluate();
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("newValue");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testDontSetButRestoreIfValueChangedOnSuccess() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "sameValue");
+ when(mStateManager.get()).thenReturn("sameValue", "changed");
+
+ rule.apply(mStatement, mDescription).evaluate();
+
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStatement, times(1)).evaluate();
+ verify(mStateManager, times(1)).set("sameValue");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testSetAndRestoreOnFailure() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "newValue");
+ when(mStateManager.get()).thenReturn("before", "changed");
+ doThrow(mRuntimeException).when(mStatement).evaluate();
+
+ final RuntimeException actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mStatement, mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("newValue");
+ verify(mStateManager, times(1)).set("before");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testDontSetIfSameValueOnFailure() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "sameValue");
+ when(mStateManager.get()).thenReturn("sameValue");
+ doThrow(mRuntimeException).when(mStatement).evaluate();
+
+ final RuntimeException actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mStatement, mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+
+ verify(mStateManager, never()).set(anyString());
+ }
+
+ @Test
+ public void testSetButDontRestoreIfSameValueOnFailure() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "newValue");
+ when(mStateManager.get()).thenReturn("before", "before");
+ doThrow(mRuntimeException).when(mStatement).evaluate();
+
+ final RuntimeException actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mStatement, mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("newValue");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testDontSetButRestoreIfValueChangedOnFailure() throws Throwable {
+ final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
+ "sameValue");
+ when(mStateManager.get()).thenReturn("sameValue", "changed");
+ doThrow(mRuntimeException).when(mStatement).evaluate();
+
+ final RuntimeException actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mStatement, mDescription).evaluate());
+ assertThat(actualException).isSameAs(mRuntimeException);
+
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("sameValue");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
new file mode 100644
index 0000000..4d5ced7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRule.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.Objects;
+
+/**
+ * JUnit rule used to restore a state after the test is run.
+ *
+ * <p>It stores the current state before the test, and restores it after the test (if necessary).
+ */
+public class StateKeeperRule<T> implements TestRule {
+
+ private final StateManager<T> mStateManager;
+
+ /**
+ * Default constructor.
+ *
+ * @param stateManager abstraction used to mange the state.
+ */
+ public StateKeeperRule(@NonNull StateManager<T> stateManager) {
+ mStateManager = Preconditions.checkNotNull(stateManager);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ final T previousValue = mStateManager.get();
+ try {
+ base.evaluate();
+ } finally {
+ final T currentValue = mStateManager.get();
+ if (!Objects.equals(previousValue, currentValue)) {
+ mStateManager.set(previousValue);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRuleTest.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRuleTest.java
new file mode 100644
index 0000000..6424d3c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateKeeperRuleTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StateKeeperRuleTest {
+
+ private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
+ private final Description mDescription = Description.createSuiteDescription("Whatever");
+
+ @Mock
+ private StateManager<String> mStateManager;
+
+ @Mock
+ private Statement mStatement;
+
+ @Test
+ public void testInvalidConstructor() {
+ assertThrows(NullPointerException.class, () -> new StateKeeperRule<Object>(null));
+ }
+
+ @Test
+ public void testRestoreOnSuccess() throws Throwable {
+ final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+ when(mStateManager.get()).thenReturn("before", "changed");
+
+ rule.apply(mStatement, mDescription).evaluate();
+
+ verify(mStatement, times(1)).evaluate();
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("before");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testRestoreOnFailure() throws Throwable {
+ final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+ when(mStateManager.get()).thenReturn("before", "changed");
+ doThrow(mRuntimeException).when(mStatement).evaluate();
+
+ final RuntimeException actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mStatement, mDescription).evaluate());
+
+ assertThat(actualException).isSameAs(mRuntimeException);
+ verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
+ verify(mStateManager, times(1)).set("before");
+ verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
+ }
+
+ @Test
+ public void testDoNotRestoreWhenNotChanged() throws Throwable {
+ final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+ when(mStateManager.get()).thenReturn("not_changed");
+
+ rule.apply(mStatement, mDescription).evaluate();
+
+ verify(mStatement, times(1)).evaluate();
+ verify(mStateManager, never()).set(anyString());
+ }
+
+ @Test
+ public void testDoNotRestoreOnFailure() throws Throwable {
+ final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
+ when(mStateManager.get()).thenReturn("not_changed");
+ doThrow(mRuntimeException).when(mStatement).evaluate();
+
+ final RuntimeException actualException = expectThrows(RuntimeException.class,
+ () -> rule.apply(mStatement, mDescription).evaluate());
+
+ assertThat(actualException).isSameAs(mRuntimeException);
+
+ verify(mStateManager, never()).set(anyString());
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/common/StateManager.java b/tests/autofillservice/src/android/autofillservice/cts/common/StateManager.java
new file mode 100644
index 0000000..376a555
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/StateManager.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.common;
+
+import android.support.annotation.Nullable;
+
+/**
+ * Abstraction for a state that is managed somewhere, like Android Settings.
+ */
+public interface StateManager<T> {
+
+ /**
+ * Sets a new state.
+ */
+ void set(@Nullable T value);
+
+ /**
+ * Gets the current state.
+ */
+ @Nullable T get();
+}
diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk
index bb1f8fc..8e9df3b 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -21,7 +21,13 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common voip-common org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ telephony-common \
+ voip-common \
+ org.apache.http.legacy \
+ android.test.base.stubs \
+
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner ctstestserver mockito-target-minus-junit4
diff --git a/tests/backup/AndroidManifest.xml b/tests/backup/AndroidManifest.xml
index 333045a..28745f3 100644
--- a/tests/backup/AndroidManifest.xml
+++ b/tests/backup/AndroidManifest.xml
@@ -20,6 +20,7 @@
<application>
<uses-library android:name="android.test.runner" />
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index 015f595..8e0bfa0 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -15,6 +15,7 @@
~ limitations under the License
-->
<configuration description="Config for CTS Backup test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="backup" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/backup/app/fullbackup/AndroidManifest.xml b/tests/backup/app/fullbackup/AndroidManifest.xml
index 8161a95..138c774 100644
--- a/tests/backup/app/fullbackup/AndroidManifest.xml
+++ b/tests/backup/app/fullbackup/AndroidManifest.xml
@@ -23,6 +23,8 @@
android:backupAgent="FullBackupBackupAgent"
android:label="Android Backup CTS App"
android:fullBackupOnly="true">
+ <uses-library android:name="android.test.runner" />
+
<activity
android:name=".MainActivity"
diff --git a/tests/backup/app/keyvalue/AndroidManifest.xml b/tests/backup/app/keyvalue/AndroidManifest.xml
index c99b4b4..3ed302d 100644
--- a/tests/backup/app/keyvalue/AndroidManifest.xml
+++ b/tests/backup/app/keyvalue/AndroidManifest.xml
@@ -22,6 +22,8 @@
android:allowBackup="true"
android:backupAgent="android.backup.app.KeyValueBackupAgent"
android:label="Android Key Value Backup CTS App">
+ <uses-library android:name="android.test.runner" />
+
<activity
android:name="android.backup.app.MainActivity"
android:label="Android Key Value Backup CTS App" >
diff --git a/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java b/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
index bbd0d26..343f2d9 100644
--- a/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
+++ b/tests/backup/src/android/backup/cts/BaseBackupCtsTest.java
@@ -21,14 +21,14 @@
import android.os.ParcelFileDescriptor;
import android.test.InstrumentationTestCase;
+import com.android.compatibility.common.util.LogcatInspector;
+
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
/**
* Base class for backup instrumentation tests.
@@ -41,9 +41,14 @@
private static final String LOCAL_TRANSPORT =
"android/com.android.internal.backup.LocalTransport";
- private static final int SMALL_LOGCAT_DELAY = 1000;
-
private boolean isBackupSupported;
+ private LogcatInspector mLogcatInspector =
+ new LogcatInspector() {
+ @Override
+ protected InputStream executeShellCommand(String command) throws IOException {
+ return executeStreamedShellCommand(getInstrumentation(), command);
+ }
+ };
@Override
protected void setUp() throws Exception {
@@ -73,55 +78,16 @@
return output.contains("* " + LOCAL_TRANSPORT);
}
- /**
- * Attempts to clear logcat.
- *
- * Clearing logcat is known to be unreliable, so this methods also output a unique separator
- * that can be used to find this point in the log even if clearing failed.
- * @return a unique separator string
- * @throws Exception
- */
- protected String clearLogcat() throws Exception {
- exec("logcat -c");
- String uniqueString = ":::" + UUID.randomUUID().toString();
- exec("log -t " + APP_LOG_TAG + " " + uniqueString);
- return uniqueString;
+ /** See {@link LogcatInspector#mark(String)}. */
+ protected String markLogcat() throws Exception {
+ return mLogcatInspector.mark(APP_LOG_TAG);
}
- /**
- * Wait for up to maxTimeoutInSeconds for the given strings to appear in the logcat in the given order.
- * By passing the separator returned by {@link #clearLogcat} as the first string you can ensure that only
- * logs emitted after that call to clearLogcat are found.
- *
- * @throws AssertionError if the strings are not found in the given time.
- */
+ /** See {@link LogcatInspector#assertLogcatContainsInOrder(String, int, String...)}. */
protected void waitForLogcat(int maxTimeoutInSeconds, String... logcatStrings)
- throws Exception {
- long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(maxTimeoutInSeconds);
- int stringIndex = 0;
- while (timeout >= System.currentTimeMillis()) {
- FileInputStream fis = executeStreamedShellCommand(getInstrumentation(),
- "logcat -v brief -d " + APP_LOG_TAG + ":* *:S");
- BufferedReader log = new BufferedReader(new InputStreamReader(fis));
- String line;
- stringIndex = 0;
- while ((line = log.readLine()) != null) {
- if (line.contains(logcatStrings[stringIndex])) {
- stringIndex++;
- if (stringIndex >= logcatStrings.length) {
- drainAndClose(log);
- return;
- }
- }
- }
- closeQuietly(log);
- // In case the key has not been found, wait for the log to update before
- // performing the next search.
- Thread.sleep(SMALL_LOGCAT_DELAY);
- }
- fail("Couldn't find " + logcatStrings[stringIndex] +
- (stringIndex > 0 ? " after " + logcatStrings[stringIndex - 1] : "") +
- " within " + maxTimeoutInSeconds + " seconds ");
+ throws Exception {
+ mLogcatInspector.assertLogcatContainsInOrder(
+ APP_LOG_TAG + ":* *:S", maxTimeoutInSeconds, logcatStrings);
}
protected void createTestFileOfSize(String packageName, int size) throws Exception {
@@ -145,8 +111,8 @@
}
}
- private static FileInputStream executeStreamedShellCommand(Instrumentation instrumentation,
- String command) throws Exception {
+ private static FileInputStream executeStreamedShellCommand(
+ Instrumentation instrumentation, String command) throws IOException {
final ParcelFileDescriptor pfd =
instrumentation.getUiAutomation().executeShellCommand(command);
return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
diff --git a/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java b/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
index beefa01..9f81ecd 100644
--- a/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
+++ b/tests/backup/src/android/backup/cts/FullBackupLifecycleTest.java
@@ -31,7 +31,7 @@
if (!isBackupSupported()) {
return;
}
- String backupSeparator = clearLogcat();
+ String backupSeparator = markLogcat();
// Make sure there's something to backup
createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
@@ -45,7 +45,7 @@
"Full backup requested",
"onDestroy");
- String restoreSeparator = clearLogcat();
+ String restoreSeparator = markLogcat();
// Now request restore and wait for it to complete
exec("bmgr restore " + BACKUP_APP_NAME);
diff --git a/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java b/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
index 3924e87..56d489d 100644
--- a/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/FullBackupQuotaTest.java
@@ -36,7 +36,7 @@
if (!isBackupSupported()) {
return;
}
- String separator = clearLogcat();
+ String separator = markLogcat();
// Launch test app and create file exceeding limit for local transport
createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
@@ -59,7 +59,7 @@
Thread.sleep(3000);
} catch (InterruptedException e) {}
- String separator = clearLogcat();
+ String separator = markLogcat();
exec("bmgr backupnow " + BACKUP_APP_NAME);
waitForLogcat(TIMEOUT_SECONDS,separator,
"quota is " + LOCAL_TRANSPORT_BACKUP_QUOTA);
diff --git a/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java b/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
index d957bbc..0bb5243 100644
--- a/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueLifecycleTest.java
@@ -31,7 +31,7 @@
if (!isBackupSupported()) {
return;
}
- String backupSeparator = clearLogcat();
+ String backupSeparator = markLogcat();
// Make sure there's something to backup
createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
@@ -44,7 +44,7 @@
"Backup requested",
"onDestroy");
- String restoreSeparator = clearLogcat();
+ String restoreSeparator = markLogcat();
// Now request restore and wait for it to complete
exec("bmgr restore " + BACKUP_APP_NAME);
diff --git a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
index c29f810..6fca9ad 100644
--- a/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
+++ b/tests/backup/src/android/backup/cts/KeyValueQuotaTest.java
@@ -36,7 +36,7 @@
if (!isBackupSupported()) {
return;
}
- String separator = clearLogcat();
+ String separator = markLogcat();
// Launch test app and create file exceeding limit for local transport
createTestFileOfSize(BACKUP_APP_NAME, LOCAL_TRANSPORT_EXCEEDING_FILE_SIZE);
@@ -50,7 +50,7 @@
if (!isBackupSupported()) {
return;
}
- String separator = clearLogcat();
+ String separator = markLogcat();
exec("bmgr backupnow " + BACKUP_APP_NAME);
waitForLogcat(TIMEOUT_SECONDS, separator,
"quota is " + LOCAL_TRANSPORT_BACKUP_QUOTA);
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
index c8ba9cd..4160f84 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -64,7 +64,7 @@
LOCAL_SDK_VERSION := test_current
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
cts_runtime_hint := 120
diff --git a/tests/camera/AndroidTest.xml b/tests/camera/AndroidTest.xml
index c046a59..2a2cdc9 100644
--- a/tests/camera/AndroidTest.xml
+++ b/tests/camera/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Camera test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="camera" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/camera/api25test/AndroidTest.xml b/tests/camera/api25test/AndroidTest.xml
index a7b2ae7..45f5d28 100644
--- a/tests/camera/api25test/AndroidTest.xml
+++ b/tests/camera/api25test/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Camera API 25 test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="camera" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/camera/libctscamera2jni/Android.mk b/tests/camera/libctscamera2jni/Android.mk
index 649908d..2cd4f64 100644
--- a/tests/camera/libctscamera2jni/Android.mk
+++ b/tests/camera/libctscamera2jni/Android.mk
@@ -44,7 +44,7 @@
libz \
# NDK build, shared C++ runtime
-LOCAL_SDK_VERSION := 24
+LOCAL_SDK_VERSION := current
LOCAL_NDK_STL_VARIANT := c++_shared
include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index b2124ce..1a35a9f 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -18,9 +18,12 @@
#define LOG_TAG "NativeCamera"
#include <log/log.h>
+#include <chrono>
+#include <condition_variable>
#include <string>
#include <map>
#include <mutex>
+#include <vector>
#include <unistd.h>
#include <assert.h>
#include <jni.h>
@@ -238,6 +241,173 @@
int mOnActive = 0;
};
+class CaptureResultListener {
+ public:
+ ~CaptureResultListener() {
+ std::unique_lock<std::mutex> l(mMutex);
+ clearSavedRequestsLocked();
+ }
+
+ static void onCaptureStart(void* /*obj*/, ACameraCaptureSession* /*session*/,
+ const ACaptureRequest* /*request*/, int64_t /*timestamp*/) {
+ //Not used for now
+ }
+
+ static void onCaptureProgressed(void* /*obj*/, ACameraCaptureSession* /*session*/,
+ ACaptureRequest* /*request*/, const ACameraMetadata* /*result*/) {
+ //Not used for now
+ }
+
+ static void onCaptureCompleted(void* obj, ACameraCaptureSession* /*session*/,
+ ACaptureRequest* request, const ACameraMetadata* result) {
+ ALOGV("%s", __FUNCTION__);
+ if ((obj == nullptr) || (result == nullptr)) {
+ return;
+ }
+ CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+ std::lock_guard<std::mutex> lock(thiz->mMutex);
+ ACameraMetadata_const_entry entry;
+ auto ret = ACameraMetadata_getConstEntry(result, ACAMERA_SYNC_FRAME_NUMBER, &entry);
+ if (ret != ACAMERA_OK) {
+ ALOGE("Error: Sync frame number missing from result!");
+ return;
+ }
+
+ if (thiz->mSaveCompletedRequests) {
+ thiz->mCompletedRequests.push_back(ACaptureRequest_copy(request));
+ }
+
+ thiz->mLastCompletedFrameNumber = entry.data.i64[0];
+ thiz->mResultCondition.notify_one();
+ }
+
+ static void onCaptureFailed(void* obj, ACameraCaptureSession* /*session*/,
+ ACaptureRequest* /*request*/, ACameraCaptureFailure* failure) {
+ ALOGV("%s", __FUNCTION__);
+ if ((obj == nullptr) || (failure == nullptr)) {
+ return;
+ }
+ CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+ std::lock_guard<std::mutex> lock(thiz->mMutex);
+ thiz->mLastFailedFrameNumber = failure->frameNumber;
+ thiz->mResultCondition.notify_one();
+ }
+
+ static void onCaptureSequenceCompleted(void* obj, ACameraCaptureSession* /*session*/,
+ int sequenceId, int64_t frameNumber) {
+ ALOGV("%s", __FUNCTION__);
+ if (obj == nullptr) {
+ return;
+ }
+ CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+ std::lock_guard<std::mutex> lock(thiz->mMutex);
+ thiz->mLastSequenceIdCompleted = sequenceId;
+ thiz->mLastSequenceFrameNumber = frameNumber;
+ thiz->mResultCondition.notify_one();
+ }
+
+ static void onCaptureSequenceAborted(void* /*obj*/, ACameraCaptureSession* /*session*/,
+ int /*sequenceId*/) {
+ //Not used for now
+ }
+
+ static void onCaptureBufferLost(void* obj, ACameraCaptureSession* /*session*/,
+ ACaptureRequest* /*request*/, ANativeWindow* /*window*/, int64_t frameNumber) {
+ ALOGV("%s", __FUNCTION__);
+ if (obj == nullptr) {
+ return;
+ }
+ CaptureResultListener* thiz = reinterpret_cast<CaptureResultListener*>(obj);
+ std::lock_guard<std::mutex> lock(thiz->mMutex);
+ thiz->mLastLostFrameNumber = frameNumber;
+ thiz->mResultCondition.notify_one();
+ }
+
+ int64_t getCaptureSequenceLastFrameNumber(int64_t sequenceId, uint32_t timeoutSec) {
+ int64_t ret = -1;
+ std::unique_lock<std::mutex> l(mMutex);
+
+ while (mLastSequenceIdCompleted != sequenceId) {
+ auto timeout = std::chrono::system_clock::now() +
+ std::chrono::seconds(timeoutSec);
+ if (std::cv_status::timeout == mResultCondition.wait_until(l, timeout)) {
+ break;
+ }
+ }
+
+ if (mLastSequenceIdCompleted == sequenceId) {
+ ret = mLastSequenceFrameNumber;
+ }
+
+ return ret;
+ }
+
+ bool waitForFrameNumber(int64_t frameNumber, uint32_t timeoutSec) {
+ bool ret = false;
+ std::unique_lock<std::mutex> l(mMutex);
+
+ while ((mLastCompletedFrameNumber != frameNumber) &&
+ (mLastLostFrameNumber != frameNumber) &&
+ (mLastFailedFrameNumber != frameNumber)) {
+ auto timeout = std::chrono::system_clock::now() +
+ std::chrono::seconds(timeoutSec);
+ if (std::cv_status::timeout == mResultCondition.wait_until(l, timeout)) {
+ break;
+ }
+ }
+
+ if ((mLastCompletedFrameNumber == frameNumber) ||
+ (mLastLostFrameNumber == frameNumber) ||
+ (mLastFailedFrameNumber == frameNumber)) {
+ ret = true;
+ }
+
+ return ret;
+ }
+
+ void setRequestSave(bool enable) {
+ std::unique_lock<std::mutex> l(mMutex);
+ if (!enable) {
+ clearSavedRequestsLocked();
+ }
+ mSaveCompletedRequests = enable;
+ }
+
+ // The lifecycle of returned ACaptureRequest* is still managed by CaptureResultListener
+ void getCompletedRequests(std::vector<ACaptureRequest*>* out) {
+ std::unique_lock<std::mutex> l(mMutex);
+ *out = mCompletedRequests;
+ }
+
+ void reset() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mLastSequenceIdCompleted = -1;
+ mLastSequenceFrameNumber = -1;
+ mLastCompletedFrameNumber = -1;
+ mLastLostFrameNumber = -1;
+ mLastFailedFrameNumber = -1;
+ mSaveCompletedRequests = false;
+ clearSavedRequestsLocked();
+ }
+
+ private:
+ std::mutex mMutex;
+ std::condition_variable mResultCondition;
+ int mLastSequenceIdCompleted = -1;
+ int64_t mLastSequenceFrameNumber = -1;
+ int64_t mLastCompletedFrameNumber = -1;
+ int64_t mLastLostFrameNumber = -1;
+ int64_t mLastFailedFrameNumber = -1;
+ bool mSaveCompletedRequests = false;
+ std::vector<ACaptureRequest*> mCompletedRequests;
+
+ void clearSavedRequestsLocked() {
+ for (ACaptureRequest* req : mCompletedRequests) {
+ ACaptureRequest_free(req);
+ }
+ mCompletedRequests.clear();
+ }
+};
class ImageReaderListener {
public:
@@ -401,6 +571,7 @@
// Free all resources except camera manager
void resetCamera() {
mSessionListener.reset();
+ mResultListener.reset();
if (mSession) {
ACameraCaptureSession_close(mSession);
mSession = nullptr;
@@ -502,6 +673,16 @@
return mCameraIdList->cameraIds[idx];
}
+ camera_status_t updateOutput(JNIEnv* env, ACaptureSessionOutput *output) {
+ if (mSession == nullptr) {
+ ALOGE("Testcase cannot update output configuration session %p",
+ mSession);
+ return ACAMERA_ERROR_UNKNOWN;
+ }
+
+ return ACameraCaptureSession_updateSharedOutput(mSession, output);
+ }
+
camera_status_t openCamera(const char* cameraId) {
if (mDevice) {
ALOGE("Cannot open camera before closing previously open one");
@@ -573,7 +754,8 @@
return mPreviewAnw;
}
- camera_status_t createCaptureSessionWithLog() {
+ camera_status_t createCaptureSessionWithLog(bool isPreviewShared = false,
+ ACaptureRequest *sessionParameters = nullptr) {
if (mSession) {
LOG_ERROR(errorString, "Cannot create session before closing existing one");
return ACAMERA_ERROR_UNKNOWN;
@@ -611,7 +793,11 @@
}
if (mPreviewInited) {
- ret = ACaptureSessionOutput_create(mPreviewAnw, &mPreviewOutput);
+ if (isPreviewShared) {
+ ret = ACaptureSessionSharedOutput_create(mPreviewAnw, &mPreviewOutput);
+ } else {
+ ret = ACaptureSessionOutput_create(mPreviewAnw, &mPreviewOutput);
+ }
if (ret != ACAMERA_OK || mPreviewOutput == nullptr) {
LOG_ERROR(errorString,
"Sesssion preview output create fail! ret %d output %p",
@@ -629,8 +815,8 @@
}
}
- ret = ACameraDevice_createCaptureSession(
- mDevice, mOutputs, &mSessionCb, &mSession);
+ ret = ACameraDevice_createCaptureSessionWithSessionParameters(
+ mDevice, mOutputs, sessionParameters, &mSessionCb, &mSession);
if (ret != ACAMERA_OK || mSession == nullptr) {
LOG_ERROR(errorString, "Create session for camera %s failed. ret %d session %p",
mCameraId, ret, mSession);
@@ -743,15 +929,69 @@
return ACAMERA_OK;
}
- camera_status_t startPreview() {
+ // The output ACaptureRequest* is still managed by testcase class
+ camera_status_t getStillRequest(ACaptureRequest** out) {
+ if (mStillRequest == nullptr) {
+ ALOGE("Camera %s Still capture request hasn't been created", mCameraId);
+ return ACAMERA_ERROR_INVALID_PARAMETER;
+ }
+ *out = mStillRequest;
+ return ACAMERA_OK;
+ }
+
+ camera_status_t getPreviewRequest(ACaptureRequest** out) {
+ if (mPreviewRequest == nullptr) {
+ ALOGE("Camera %s Preview capture request hasn't been created", mCameraId);
+ return ACAMERA_ERROR_INVALID_PARAMETER;
+ }
+ *out = mPreviewRequest;
+ return ACAMERA_OK;
+ }
+
+ camera_status_t startPreview(int *sequenceId = nullptr) {
if (mSession == nullptr || mPreviewRequest == nullptr) {
ALOGE("Testcase cannot start preview: session %p, preview request %p",
mSession, mPreviewRequest);
return ACAMERA_ERROR_UNKNOWN;
}
int previewSeqId;
- return ACameraCaptureSession_setRepeatingRequest(
- mSession, nullptr, 1, &mPreviewRequest, &previewSeqId);
+ camera_status_t ret;
+ if (sequenceId == nullptr) {
+ ret = ACameraCaptureSession_setRepeatingRequest(
+ mSession, nullptr, 1, &mPreviewRequest, &previewSeqId);
+ } else {
+ ret = ACameraCaptureSession_setRepeatingRequest(
+ mSession, &mResultCb, 1, &mPreviewRequest, sequenceId);
+ }
+ return ret;
+ }
+
+ camera_status_t updateRepeatingRequest(ACaptureRequest *updatedRequest,
+ int *sequenceId = nullptr) {
+ if (mSession == nullptr || updatedRequest == nullptr) {
+ ALOGE("Testcase cannot update repeating request: session %p, updated request %p",
+ mSession, updatedRequest);
+ return ACAMERA_ERROR_UNKNOWN;
+ }
+
+ int previewSeqId;
+ camera_status_t ret;
+ if (sequenceId == nullptr) {
+ ret = ACameraCaptureSession_setRepeatingRequest(
+ mSession, nullptr, 1, &updatedRequest, &previewSeqId);
+ } else {
+ ret = ACameraCaptureSession_setRepeatingRequest(
+ mSession, &mResultCb, 1, &updatedRequest, sequenceId);
+ }
+ return ret;
+ }
+
+ int64_t getCaptureSequenceLastFrameNumber(int64_t sequenceId, uint32_t timeoutSec) {
+ return mResultListener.getCaptureSequenceLastFrameNumber(sequenceId, timeoutSec);
+ }
+
+ bool waitForFrameNumber(int64_t frameNumber, uint32_t timeoutSec) {
+ return mResultListener.waitForFrameNumber(frameNumber, timeoutSec);
}
camera_status_t takePicture() {
@@ -765,6 +1005,18 @@
mSession, nullptr, 1, &mStillRequest, &seqId);
}
+ camera_status_t capture(ACaptureRequest* request,
+ ACameraCaptureSession_captureCallbacks* listener,
+ /*out*/int* seqId) {
+ if (mSession == nullptr || request == nullptr) {
+ ALOGE("Testcase cannot capture session: session %p, request %p",
+ mSession, request);
+ return ACAMERA_ERROR_UNKNOWN;
+ }
+ return ACameraCaptureSession_capture(
+ mSession, listener, 1, &request, seqId);
+ }
+
camera_status_t resetWithErrorLog() {
camera_status_t ret;
@@ -785,6 +1037,7 @@
return ACAMERA_ERROR_UNKNOWN;
}
mSessionListener.reset();
+ mResultListener.reset();
ret = closeCamera();
if (ret != ACAMERA_OK) {
@@ -800,6 +1053,14 @@
return &mSessionListener;
}
+ ACameraDevice* getCameraDevice() {
+ return mDevice;
+ }
+
+ ACaptureSessionOutput *getPreviewOutput() {
+ return mPreviewOutput;
+ }
+
private:
ACameraManager* createManager() {
if (!mCameraManager) {
@@ -828,6 +1089,18 @@
CaptureSessionListener::onActive
};
+ CaptureResultListener mResultListener;
+ ACameraCaptureSession_captureCallbacks mResultCb {
+ &mResultListener,
+ CaptureResultListener::onCaptureStart,
+ CaptureResultListener::onCaptureProgressed,
+ CaptureResultListener::onCaptureCompleted,
+ CaptureResultListener::onCaptureFailed,
+ CaptureResultListener::onCaptureSequenceCompleted,
+ CaptureResultListener::onCaptureSequenceAborted,
+ CaptureResultListener::onCaptureBufferLost
+ };
+
ACameraIdList* mCameraIdList = nullptr;
ACameraDevice* mDevice = nullptr;
AImageReader* mImgReader = nullptr;
@@ -1241,6 +1514,34 @@
}
}
+ void* context = nullptr;
+ ret = ACaptureRequest_getUserContext(request, &context);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Get capture request context failed: ret %d", ret);
+ goto cleanup;
+ }
+ if (context != nullptr) {
+ LOG_ERROR(errorString, "Capture request context is not null: %p", context);
+ goto cleanup;
+ }
+
+ intptr_t magic_num = 0xBEEF;
+ ret = ACaptureRequest_setUserContext(request, (void*) magic_num);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Set capture request context failed: ret %d", ret);
+ goto cleanup;
+ }
+
+ ret = ACaptureRequest_getUserContext(request, &context);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Get capture request context failed: ret %d", ret);
+ goto cleanup;
+ }
+ if (context != (void*) magic_num) {
+ LOG_ERROR(errorString, "Capture request context is wrong: %p", context);
+ goto cleanup;
+ }
+
// try get/set capture request fields
ACameraMetadata_const_entry entry;
ret = ACaptureRequest_getConstEntry(request, ACAMERA_CONTROL_AE_MODE, &entry);
@@ -1483,6 +1784,263 @@
extern "C" jboolean
Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
+testCameraDeviceSharedOutputUpdate(
+ JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface, jobject jSharedSurface) {
+ ALOGV("%s", __FUNCTION__);
+ int numCameras = 0;
+ bool pass = false;
+ PreviewTestCase testCase;
+ int sequenceId = -1;
+ int64_t lastFrameNumber = 0;
+ bool frameArrived = false;
+ ANativeWindow* previewAnw = nullptr;
+ ANativeWindow* sharedAnw = ANativeWindow_fromSurface(env, jSharedSurface);
+ ACaptureRequest* updatedRequest = nullptr;
+ ACameraOutputTarget* reqPreviewOutput = nullptr;
+ ACameraOutputTarget* reqSharedOutput = nullptr;
+ ACaptureSessionOutput *previewOutput = nullptr;
+ uint32_t timeoutSec = 1;
+ uint32_t runPreviewSec = 2;
+
+ camera_status_t ret = testCase.initWithErrorLog();
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ numCameras = testCase.getNumCameras();
+ if (numCameras < 0) {
+ LOG_ERROR(errorString, "Testcase returned negavtive number of cameras: %d", numCameras);
+ goto cleanup;
+ }
+
+ for (int i = 0; i < numCameras; i++) {
+ const char* cameraId = testCase.getCameraId(i);
+ if (cameraId == nullptr) {
+ LOG_ERROR(errorString, "Testcase returned null camera id for camera %d", i);
+ goto cleanup;
+ }
+
+ ret = testCase.openCamera(cameraId);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Open camera device %s failure. ret %d", cameraId, ret);
+ goto cleanup;
+ }
+
+ usleep(100000); // sleep to give some time for callbacks to happen
+
+ if (testCase.isCameraAvailable(cameraId)) {
+ LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
+ goto cleanup;
+ }
+
+ previewAnw = testCase.initPreviewAnw(env, jPreviewSurface);
+ if (previewAnw == nullptr) {
+ LOG_ERROR(errorString, "Null ANW from preview surface!");
+ goto cleanup;
+ }
+
+ ret = testCase.createCaptureSessionWithLog(true);
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ ret = testCase.createRequestsWithErrorLog();
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ ret = testCase.startPreview();
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Start preview failed!");
+ goto cleanup;
+ }
+
+ sleep(runPreviewSec);
+
+ previewOutput = testCase.getPreviewOutput();
+ //Try some bad input
+ ret = ACaptureSessionSharedOutput_add(previewOutput, previewAnw);
+ if (ret != ACAMERA_ERROR_INVALID_PARAMETER) {
+ LOG_ERROR(errorString, "ACaptureSessionSharedOutput_add should return invalid "
+ "parameter! %d", ret);
+ goto cleanup;
+ }
+
+ ret = ACaptureSessionSharedOutput_remove(previewOutput, previewAnw);
+ if (ret != ACAMERA_ERROR_INVALID_PARAMETER) {
+ LOG_ERROR(errorString, "ACaptureSessionSharedOutput_remove should return invalid "
+ "parameter! %d", ret);
+ goto cleanup;
+ }
+
+ //Now try with valid input
+ ret = ACaptureSessionSharedOutput_add(previewOutput, sharedAnw);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "ACaptureSessionSharedOutput_add failed!")
+ goto cleanup;
+ }
+
+ ret = testCase.updateOutput(env, previewOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Failed to update output configuration!")
+ goto cleanup;
+ }
+
+ ret = ACameraDevice_createCaptureRequest(
+ testCase.getCameraDevice(), TEMPLATE_PREVIEW, &updatedRequest);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s create preview request failed. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ ret = ACameraOutputTarget_create(previewAnw, &reqPreviewOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString,
+ "Camera %s create request preview output target failed. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ ret = ACaptureRequest_addTarget(updatedRequest, reqPreviewOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s add preview request output failed. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ ret = ACameraOutputTarget_create(sharedAnw, &reqSharedOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString,
+ "Camera %s create request preview output target failed. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ ret = ACaptureRequest_addTarget(updatedRequest, reqSharedOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s add preview request output failed. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ ret = testCase.updateRepeatingRequest(updatedRequest, &sequenceId);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s failed to update repeated request. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ sleep(runPreviewSec);
+
+ ret = ACaptureSessionSharedOutput_remove(previewOutput, sharedAnw);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "ACaptureSessionSharedOutput_remove failed!");
+ goto cleanup;
+ }
+
+ //Try removing shared output which still has pending camera requests
+ ret = testCase.updateOutput(env, previewOutput);
+ if (ret != ACAMERA_ERROR_INVALID_PARAMETER) {
+ LOG_ERROR(errorString, "updateOutput should fail!");
+ goto cleanup;
+ }
+
+ //Remove the shared output correctly by updating the repeating request
+ //first
+ ret = ACaptureRequest_removeTarget(updatedRequest, reqSharedOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s remove target output failed. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ ret = testCase.updateRepeatingRequest(updatedRequest);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s failed to update repeated request. ret %d",
+ cameraId, ret);
+ goto cleanup;
+ }
+
+ //Then wait for all old requests to flush
+ lastFrameNumber = testCase.getCaptureSequenceLastFrameNumber(sequenceId, timeoutSec);
+ if (lastFrameNumber < 0) {
+ LOG_ERROR(errorString, "Camera %s failed to acquire last frame number!",
+ cameraId);
+ goto cleanup;
+ }
+
+ frameArrived = testCase.waitForFrameNumber(lastFrameNumber, timeoutSec);
+ if (!frameArrived) {
+ LOG_ERROR(errorString, "Camera %s timed out waiting on last frame number!",
+ cameraId);
+ goto cleanup;
+ }
+
+ ret = testCase.updateOutput(env, previewOutput);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "updateOutput failed!");
+ goto cleanup;
+ }
+
+ sleep(runPreviewSec);
+
+ ret = testCase.resetWithErrorLog();
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ usleep(100000); // sleep to give some time for callbacks to happen
+
+ if (!testCase.isCameraAvailable(cameraId)) {
+ LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
+ goto cleanup;
+ }
+ }
+
+ ret = testCase.deInit();
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Testcase deInit failed: ret %d", ret);
+ goto cleanup;
+ }
+
+ pass = true;
+
+cleanup:
+
+ if (updatedRequest != nullptr) {
+ ACaptureRequest_free(updatedRequest);
+ updatedRequest = nullptr;
+ }
+
+ if (reqPreviewOutput != nullptr) {
+ ACameraOutputTarget_free(reqPreviewOutput);
+ reqPreviewOutput = nullptr;
+ }
+
+ if (reqSharedOutput != nullptr) {
+ ACameraOutputTarget_free(reqSharedOutput);
+ reqSharedOutput = nullptr;
+ }
+
+ if (sharedAnw) {
+ ANativeWindow_release(sharedAnw);
+ sharedAnw = nullptr;
+ }
+
+ ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
+ if (!pass) {
+ throwAssertionError(env, errorString);
+ }
+ return pass;
+}
+
+extern "C" jboolean
+Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
testCameraDeviceSimplePreviewNative(
JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface) {
ALOGV("%s", __FUNCTION__);
@@ -1577,6 +2135,134 @@
return pass;
}
+extern "C" jboolean
+Java_android_hardware_camera2_cts_NativeCameraDeviceTest_\
+testCameraDevicePreviewWithSessionParametersNative(
+ JNIEnv* env, jclass /*clazz*/, jobject jPreviewSurface) {
+ ALOGV("%s", __FUNCTION__);
+ int numCameras = 0;
+ bool pass = false;
+ ACameraManager* mgr = ACameraManager_create();
+ ACameraMetadata* chars = nullptr;
+ PreviewTestCase testCase;
+
+ camera_status_t ret = testCase.initWithErrorLog();
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ numCameras = testCase.getNumCameras();
+ if (numCameras < 0) {
+ LOG_ERROR(errorString, "Testcase returned negavtive number of cameras: %d", numCameras);
+ goto cleanup;
+ }
+
+ for (int i = 0; i < numCameras; i++) {
+ const char* cameraId = testCase.getCameraId(i);
+ if (cameraId == nullptr) {
+ LOG_ERROR(errorString, "Testcase returned null camera id for camera %d", i);
+ goto cleanup;
+ }
+
+ ret = ACameraManager_getCameraCharacteristics(
+ mgr, cameraId, &chars);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Get camera characteristics failed: ret %d", ret);
+ goto cleanup;
+ }
+
+ ACameraMetadata_const_entry sessionParamKeys{};
+ ret = ACameraMetadata_getConstEntry(chars, ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS,
+ &sessionParamKeys);
+ if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0)) {
+ ACameraMetadata_free(chars);
+ chars = nullptr;
+ continue;
+ }
+
+ ret = testCase.openCamera(cameraId);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Open camera device %s failure. ret %d", cameraId, ret);
+ goto cleanup;
+ }
+
+ usleep(100000); // sleep to give some time for callbacks to happen
+
+ if (testCase.isCameraAvailable(cameraId)) {
+ LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
+ goto cleanup;
+ }
+
+ ANativeWindow* previewAnw = testCase.initPreviewAnw(env, jPreviewSurface);
+ if (previewAnw == nullptr) {
+ LOG_ERROR(errorString, "Null ANW from preview surface!");
+ goto cleanup;
+ }
+
+ ret = testCase.createRequestsWithErrorLog();
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ ACaptureRequest *previewRequest = nullptr;
+ ret = testCase.getPreviewRequest(&previewRequest);
+ if ((ret != ACAMERA_OK) || (previewRequest == nullptr)) {
+ LOG_ERROR(errorString, "Preview request query failed!");
+ goto cleanup;
+ }
+
+ ret = testCase.createCaptureSessionWithLog(/*isPreviewShared*/ false, previewRequest);
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ ret = testCase.startPreview();
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Start preview failed!");
+ goto cleanup;
+ }
+
+ sleep(3);
+
+ ret = testCase.resetWithErrorLog();
+ if (ret != ACAMERA_OK) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
+ usleep(100000); // sleep to give some time for callbacks to happen
+
+ if (!testCase.isCameraAvailable(cameraId)) {
+ LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
+ goto cleanup;
+ }
+
+ ACameraMetadata_free(chars);
+ chars = nullptr;
+ }
+
+ ret = testCase.deInit();
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Testcase deInit failed: ret %d", ret);
+ goto cleanup;
+ }
+
+ pass = true;
+cleanup:
+ if (chars) {
+ ACameraMetadata_free(chars);
+ }
+ ACameraManager_delete(mgr);
+ ALOGI("%s %s", __FUNCTION__, pass ? "pass" : "failed");
+ if (!pass) {
+ throwAssertionError(env, errorString);
+ }
+ return pass;
+}
+
bool nativeImageReaderTestBase(
JNIEnv* env, jstring jOutPath, AImageReader_ImageCallback cb) {
const int NUM_TEST_IMAGES = 10;
@@ -1649,14 +2335,68 @@
goto cleanup;
}
+ CaptureResultListener resultListener;
+ ACameraCaptureSession_captureCallbacks resultCb {
+ &resultListener,
+ CaptureResultListener::onCaptureStart,
+ CaptureResultListener::onCaptureProgressed,
+ CaptureResultListener::onCaptureCompleted,
+ CaptureResultListener::onCaptureFailed,
+ CaptureResultListener::onCaptureSequenceCompleted,
+ CaptureResultListener::onCaptureSequenceAborted,
+ CaptureResultListener::onCaptureBufferLost
+ };
+ resultListener.setRequestSave(true);
+ ACaptureRequest* requestTemplate = nullptr;
+ ret = testCase.getStillRequest(&requestTemplate);
+ if (ret != ACAMERA_OK || requestTemplate == nullptr) {
+ // Don't log error here. testcase did it
+ goto cleanup;
+ }
+
// Do some still capture
- for (int capture = 0; capture < NUM_TEST_IMAGES; capture++) {
- ret = testCase.takePicture();
+ int lastSeqId = -1;
+ for (intptr_t capture = 0; capture < NUM_TEST_IMAGES; capture++) {
+ ACaptureRequest* req = ACaptureRequest_copy(requestTemplate);
+ ACaptureRequest_setUserContext(req, (void*) capture);
+ int seqId;
+ ret = testCase.capture(req, &resultCb, &seqId);
if (ret != ACAMERA_OK) {
- LOG_ERROR(errorString, "Camera %s capture(%d) failed. ret %d",
+ LOG_ERROR(errorString, "Camera %s capture(%" PRIdPTR ") failed. ret %d",
cameraId, capture, ret);
goto cleanup;
}
+ if (capture == NUM_TEST_IMAGES - 1) {
+ lastSeqId = seqId;
+ }
+ ACaptureRequest_free(req);
+ }
+
+ // wait until last sequence complete
+ resultListener.getCaptureSequenceLastFrameNumber(lastSeqId, /*timeoutSec*/ 3);
+
+ std::vector<ACaptureRequest*> completedRequests;
+ resultListener.getCompletedRequests(&completedRequests);
+
+ if (completedRequests.size() != NUM_TEST_IMAGES) {
+ LOG_ERROR(errorString, "Camera %s fails to capture %d capture results. Got %zu",
+ cameraId, NUM_TEST_IMAGES, completedRequests.size());
+ goto cleanup;
+ }
+
+ for (intptr_t i = 0; i < NUM_TEST_IMAGES; i++) {
+ intptr_t userContext = -1;
+ ret = ACaptureRequest_getUserContext(completedRequests[i], (void**) &userContext);
+ if (ret != ACAMERA_OK) {
+ LOG_ERROR(errorString, "Camera %s fails to get request user context", cameraId);
+ goto cleanup;
+ }
+
+ if (userContext != i) {
+ LOG_ERROR(errorString, "Camera %s fails to return matching user context. "
+ "Expect %" PRIdPTR ", got %" PRIdPTR, cameraId, i, userContext);
+ goto cleanup;
+ }
}
// wait until all capture finished
diff --git a/tests/camera/res/layout/multi_view.xml b/tests/camera/res/layout/multi_view.xml
index 4f335d3..e7c5508 100644
--- a/tests/camera/res/layout/multi_view.xml
+++ b/tests/camera/res/layout/multi_view.xml
@@ -29,6 +29,18 @@
android:id="@+id/texture_view_2"
android:layout_width="160dp"
android:layout_height="120dp"/>
+ <TextureView
+ android:id="@+id/texture_view_3"
+ android:layout_width="160dp"
+ android:layout_height="120dp"/>
+ <TextureView
+ android:id="@+id/texture_view_4"
+ android:layout_width="160dp"
+ android:layout_height="120dp"/>
+ <TextureView
+ android:id="@+id/texture_view_5"
+ android:layout_width="160dp"
+ android:layout_height="120dp"/>
<SurfaceView
android:id="@+id/surface_view_1"
android:layout_width="160dp"
diff --git a/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java b/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java
index d6350fc..1f4265d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java
+++ b/tests/camera/src/android/hardware/camera2/cts/Camera2MultiViewCtsActivity.java
@@ -27,7 +27,8 @@
public class Camera2MultiViewCtsActivity extends Activity {
private final static String TAG = "Camera2MultiViewCtsActivity";
- private TextureView[] mTextureView = new TextureView[2];
+ public final static int MAX_TEXTURE_VIEWS = 5;
+ private TextureView[] mTextureView = new TextureView[MAX_TEXTURE_VIEWS];
private SurfaceView[] mSurfaceView = new SurfaceView[2];
@Override
@@ -36,6 +37,9 @@
setContentView(R.layout.multi_view);
mTextureView[0] = (TextureView) findViewById(R.id.texture_view_1);
mTextureView[1] = (TextureView) findViewById(R.id.texture_view_2);
+ mTextureView[2] = (TextureView) findViewById(R.id.texture_view_3);
+ mTextureView[3] = (TextureView) findViewById(R.id.texture_view_4);
+ mTextureView[4] = (TextureView) findViewById(R.id.texture_view_5);
mSurfaceView[0] = (SurfaceView) findViewById(R.id.surface_view_1);
mSurfaceView[1] = (SurfaceView) findViewById(R.id.surface_view_2);
@@ -44,8 +48,9 @@
}
public TextureView getTextureView(int index) {
- if (index < 0 || index > 1) {
- throw new IllegalArgumentException("Texture view index must be 0 or 1");
+ if (index < 0 || index > (MAX_TEXTURE_VIEWS - 1)) {
+ throw new IllegalArgumentException("Texture view index must be between 0 and " +
+ MAX_TEXTURE_VIEWS);
}
return mTextureView[index];
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
index e1dfbc1..4bd6186 100644
--- a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
+++ b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
@@ -73,12 +73,18 @@
changeSucceeded = surfaceChangedDone.block(waitTimeMs);
if (!changeSucceeded) {
Log.e(TAG, "Wait for surface change timed out after " + timeOutMs + " ms");
- return changeSucceeded;
+ return false;
} else {
// Get a surface change callback, need to check if the size is expected.
surfaceChangedDone.close();
- if (currentWidth == expectWidth && currentHeight == expectHeight) {
- return changeSucceeded;
+ synchronized(surfaceLock) {
+ if (expectWidth == currentWidth && expectHeight == currentHeight) {
+ return true;
+ } else {
+ Log.i(TAG, "Wait for surface changed to " + expectWidth + "x" +
+ "expectHeight. Got " + currentWidth + "x" + currentHeight +
+ ". Keep waiting");
+ }
}
// Do a further iteration surface change check as surfaceChanged could be called
// again.
@@ -88,6 +94,7 @@
}
// Couldn't get expected surface size change.
+ Log.e(TAG, "Wait for surface change timed out after " + timeOutMs + " ms");
return false;
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 3d9c683..05a642c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -35,9 +35,13 @@
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.os.Handler;
import android.os.SystemClock;
@@ -783,6 +787,213 @@
}
}
+ /**
+ * Test session configuration.
+ */
+ public void testSessionConfiguration() throws Exception {
+ ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration> ();
+ outConfigs.add(new OutputConfiguration(new Size(1, 1), SurfaceTexture.class));
+ outConfigs.add(new OutputConfiguration(new Size(2, 2), SurfaceTexture.class));
+ mSessionMockListener = spy(new BlockingSessionCallback());
+ InputConfiguration inputConfig = new InputConfiguration(1, 1, ImageFormat.PRIVATE);
+
+ SessionConfiguration regularSessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR, outConfigs, mSessionMockListener, null);
+
+ SessionConfiguration highspeedSessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_HIGH_SPEED, outConfigs, mSessionMockListener, null);
+
+ assertEquals("Session configuration output doesn't match",
+ regularSessionConfig.getOutputConfigurations(), outConfigs);
+
+ assertEquals("Session configuration output doesn't match",
+ regularSessionConfig.getOutputConfigurations(),
+ highspeedSessionConfig.getOutputConfigurations());
+
+ assertEquals("Session configuration callback doesn't match",
+ regularSessionConfig.getStateCallback(), mSessionMockListener);
+
+ assertEquals("Session configuration callback doesn't match",
+ regularSessionConfig.getStateCallback(),
+ highspeedSessionConfig.getStateCallback());
+
+ assertEquals("Session configuration handler doesn't match",
+ regularSessionConfig.getHandler(), null);
+
+ assertEquals("Session configuration handler doesn't match",
+ regularSessionConfig.getHandler(), highspeedSessionConfig.getHandler());
+
+ regularSessionConfig.setInputConfiguration(inputConfig);
+ assertEquals("Session configuration input doesn't match",
+ regularSessionConfig.getInputConfiguration(), inputConfig);
+
+ try {
+ highspeedSessionConfig.setInputConfiguration(inputConfig);
+ fail("No exception for valid input configuration in hight speed session configuration");
+ } catch (UnsupportedOperationException e) {
+ //expected
+ }
+
+ assertEquals("Session configuration input doesn't match",
+ highspeedSessionConfig.getInputConfiguration(), null);
+
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i], mCameraMockListener);
+ waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+ CaptureRequest.Builder builder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ CaptureRequest request = builder.build();
+
+ regularSessionConfig.setSessionParameters(request);
+ highspeedSessionConfig.setSessionParameters(request);
+
+ assertEquals("Session configuration parameters doesn't match",
+ regularSessionConfig.getSessionParameters(), request);
+
+ assertEquals("Session configuration parameters doesn't match",
+ regularSessionConfig.getSessionParameters(),
+ highspeedSessionConfig.getSessionParameters());
+ }
+ finally {
+ closeDevice(mCameraIds[i], mCameraMockListener);
+ }
+ }
+ }
+
+ /**
+ * Verify creating a session with additional parameters.
+ */
+ public void testCreateSessionWithParameters() throws Exception {
+ for (int i = 0; i < mCameraIds.length; i++) {
+ try {
+ openDevice(mCameraIds[i], mCameraMockListener);
+ waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+ if (!mStaticInfo.isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + mCameraIds[i] +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/false);
+ testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/true);
+ }
+ finally {
+ closeDevice(mCameraIds[i], mCameraMockListener);
+ }
+ }
+ }
+
+ /**
+ * Verify creating a session with additional parameters works
+ */
+ private void testCreateSessionWithParametersByCamera(String cameraId, boolean reprocessable)
+ throws Exception {
+ final int SESSION_TIMEOUT_MS = 1000;
+ final int CAPTURE_TIMEOUT_MS = 3000;
+ int inputFormat = ImageFormat.YUV_420_888;
+ int outputFormat = inputFormat;
+ Size outputSize = mOrderedPreviewSizes.get(0);
+ Size inputSize = outputSize;
+ InputConfiguration inputConfig = null;
+
+ if (VERBOSE) {
+ Log.v(TAG, "Testing creating session with parameters for camera " + cameraId);
+ }
+
+ CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
+ StreamConfigurationMap config = characteristics.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+ if (reprocessable) {
+ //Pick a supported i/o format and size combination.
+ //Ideally the input format should match the output.
+ boolean found = false;
+ int inputFormats [] = config.getInputFormats();
+ if (inputFormats.length == 0) {
+ return;
+ }
+
+ for (int inFormat : inputFormats) {
+ int outputFormats [] = config.getValidOutputFormatsForInput(inputFormat);
+ for (int outFormat : outputFormats) {
+ if (inFormat == outFormat) {
+ inputFormat = inFormat;
+ outputFormat = outFormat;
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+
+ //In case the above combination doesn't exist, pick the first first supported
+ //pair.
+ if (!found) {
+ inputFormat = inputFormats[0];
+ int outputFormats [] = config.getValidOutputFormatsForInput(inputFormat);
+ assertTrue("No output formats supported for input format: " + inputFormat,
+ (outputFormats.length > 0));
+ outputFormat = outputFormats[0];
+ }
+
+ Size inputSizes[] = config.getInputSizes(inputFormat);
+ Size outputSizes[] = config.getOutputSizes(outputFormat);
+ assertTrue("No valid sizes supported for input format: " + inputFormat,
+ (inputSizes.length > 0));
+ assertTrue("No valid sizes supported for output format: " + outputFormat,
+ (outputSizes.length > 0));
+
+ inputSize = inputSizes[0];
+ outputSize = outputSizes[0];
+ inputConfig = new InputConfiguration(inputSize.getWidth(),
+ inputSize.getHeight(), inputFormat);
+ } else {
+ if (config.isOutputSupportedFor(outputFormat)) {
+ outputSize = config.getOutputSizes(outputFormat)[0];
+ } else {
+ return;
+ }
+ }
+
+ ImageReader imageReader = ImageReader.newInstance(outputSize.getWidth(),
+ outputSize.getHeight(), outputFormat, /*maxImages*/1);
+
+ try {
+ mSessionMockListener = spy(new BlockingSessionCallback());
+ mSessionWaiter = mSessionMockListener.getStateWaiter();
+ List<OutputConfiguration> outputs = new ArrayList<>();
+ outputs.add(new OutputConfiguration(imageReader.getSurface()));
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR, outputs, mSessionMockListener, mHandler);
+
+ CaptureRequest.Builder builder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ builder.addTarget(imageReader.getSurface());
+ CaptureRequest request = builder.build();
+
+ sessionConfig.setInputConfiguration(inputConfig);
+ sessionConfig.setSessionParameters(request);
+ mCamera.createCaptureSession(sessionConfig);
+
+ mSession = mSessionMockListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+
+ // Verify we can capture a frame with the session.
+ SimpleCaptureCallback captureListener = new SimpleCaptureCallback();
+ SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+ imageReader.setOnImageAvailableListener(imageListener, mHandler);
+
+ mSession.capture(request, captureListener, mHandler);
+ captureListener.getCaptureResultForRequest(request, CAPTURE_TIMEOUT_MS);
+ imageListener.getImage(CAPTURE_TIMEOUT_MS).close();
+ } finally {
+ imageReader.close();
+ mSession.close();
+ }
+ }
/**
* Verify creating sessions back to back and only the last one is valid for
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index b29fd57..3857b93 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -535,6 +535,10 @@
waiverKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
}
+ if (!mStaticInfo.isAfSceneChangeSupported()) {
+ waiverKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+ }
+
if (mStaticInfo.isHardwareLevelAtLeastFull()) {
return waiverKeys;
}
@@ -770,6 +774,7 @@
resultKeys.add(CaptureResult.CONTROL_AWB_STATE);
resultKeys.add(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
resultKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
+ resultKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
resultKeys.add(CaptureResult.EDGE_MODE);
resultKeys.add(CaptureResult.FLASH_MODE);
resultKeys.add(CaptureResult.FLASH_STATE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 000edcd..bde3aed 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -334,6 +334,7 @@
expectKeyAvailable(c, CameraCharacteristics.FLASH_INFO_AVAILABLE , OPT , BC );
expectKeyAvailable(c, CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES , OPT , RAW );
expectKeyAvailable(c, CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL , OPT , BC );
+ expectKeyAvailable(c, CameraCharacteristics.INFO_VERSION , OPT , NONE );
expectKeyAvailable(c, CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES , OPT , BC );
expectKeyAvailable(c, CameraCharacteristics.LENS_FACING , OPT , BC );
expectKeyAvailable(c, CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES , FULL , MANUAL_SENSOR );
@@ -406,6 +407,19 @@
expectKeyAvailable(c,
CameraCharacteristics.CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE, OPT, BC);
}
+
+ // Verify version is a short text string.
+ if (allKeys.contains(CameraCharacteristics.INFO_VERSION)) {
+ final String TEXT_REGEX = "[\\p{Alnum}\\p{Punct}\\p{Space}]*";
+ final int MAX_VERSION_LENGTH = 256;
+
+ String version = c.get(CameraCharacteristics.INFO_VERSION);
+ mCollector.expectTrue("Version contains non-text characters: " + version,
+ version.matches(TEXT_REGEX));
+ mCollector.expectLessOrEqual("Version too long: " + version, MAX_VERSION_LENGTH,
+ version.length());
+ }
+
counter++;
}
}
@@ -497,6 +511,25 @@
}
/**
+ * Test values for the available session keys.
+ */
+ public void testStaticSessionKeys() throws Exception {
+ for (CameraCharacteristics c : mCharacteristics) {
+ List<CaptureRequest.Key<?>> availableSessionKeys = c.getAvailableSessionKeys();
+ if (availableSessionKeys == null) {
+ continue;
+ }
+ List<CaptureRequest.Key<?>> availableRequestKeys = c.getAvailableCaptureRequestKeys();
+
+ //Every session key should be part of the available request keys
+ for (CaptureRequest.Key<?> key : availableSessionKeys) {
+ assertTrue("Session key:" + key.getName() + " not present in the available capture "
+ + "request keys!", availableRequestKeys.contains(key));
+ }
+ }
+ }
+
+ /**
* Test values for static metadata used by the BURST capability.
*/
public void testStaticBurstCharacteristics() throws Exception {
diff --git a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
index c4ae61b..488c0f3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
@@ -48,7 +48,6 @@
* May not take more than a few seconds to run, to be suitable for quick
* testing.
*/
-@Presubmit
public class FastBasicsTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "FastBasicsTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -58,6 +57,7 @@
private static final int WAIT_FOR_PICTURE_TIMEOUT_MS = 5000;
private static final int FRAMES_TO_WAIT_FOR_CAPTURE = 100;
+ @Presubmit
public void testCamera2() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
@@ -178,6 +178,7 @@
}
}
+ @Presubmit
public void testCamera1() throws Exception {
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera camera = null;
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index bd01d21..96f363c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -553,6 +553,21 @@
}
/**
+ * Test that images captured after discarding free buffers are valid.
+ */
+ public void testDiscardFreeBuffers() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ Log.v(TAG, "Testing jpeg capture for Camera " + id);
+ openDevice(id);
+ discardFreeBuffersTestByCamera();
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+ /**
* Convert a rectangular patch in a YUV image to an ARGB color array.
*
* @param w width of the patch.
@@ -762,6 +777,38 @@
imageInvalidAccessTestAfterClose(img, firstPlane, buffer);
}
+ /**
+ * Test that images captured after discarding free buffers are valid.
+ */
+ private void discardFreeBuffersTestByCamera() throws Exception {
+ final int FORMAT = mStaticInfo.isColorOutputSupported() ?
+ ImageFormat.YUV_420_888 : ImageFormat.DEPTH16;
+
+ final Size SIZE = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
+ StaticMetadata.StreamDirection.Output)[0];
+ Image img = null;
+ // Create ImageReader.
+ mListener = new SimpleImageListener();
+ createDefaultImageReader(SIZE, FORMAT, MAX_NUM_IMAGES, mListener);
+
+ // Start capture.
+ final boolean REPEATING = true;
+ CaptureRequest request = prepareCaptureRequest();
+ SimpleCaptureCallback listener = new SimpleCaptureCallback();
+ startCapture(request, REPEATING, listener, mHandler);
+
+ // Validate images and capture results.
+ validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING);
+ validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
+
+ // Discard free buffers.
+ mReader.discardFreeBuffers();
+
+ // Validate images and capture resulst again.
+ validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING);
+ validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
+ }
+
private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 265729d..e9eac65 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -21,13 +21,19 @@
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.cts.CameraTestUtils.ImageVerifierListener;
import android.hardware.camera2.cts.helpers.StaticMetadata;
import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase;
import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase.CameraPreviewListener;
import android.hardware.camera2.params.OutputConfiguration;
+import android.media.Image;
import android.media.ImageReader;
import android.os.SystemClock;
+import android.os.ConditionVariable;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
@@ -44,8 +50,10 @@
*/
public class MultiViewTest extends Camera2MultiViewTestCase {
private static final String TAG = "MultiViewTest";
- private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 2000;
+ private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 5000; //ms
private final static long PREVIEW_TIME_MS = 2000;
+ private final static int NUM_SURFACE_SWITCHES = 10;
+ private final static int IMG_READER_COUNT = 2;
public void testTextureViewPreview() throws Exception {
for (String cameraId : mCameraIds) {
@@ -235,6 +243,473 @@
}
/*
+ * Verify dynamic shared surface behavior.
+ */
+ public void testSharedSurfaceBasic() throws Exception {
+ for (String cameraId : mCameraIds) {
+ try {
+ openCamera(cameraId);
+ if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+ Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+ continue;
+ }
+ if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + cameraId +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testSharedSurfaceBasicByCamera(cameraId);
+ }
+ finally {
+ closeCamera(cameraId);
+ }
+ }
+ }
+
+ private void testSharedSurfaceBasicByCamera(String cameraId) throws Exception {
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+ CameraPreviewListener[] previewListener = new CameraPreviewListener[2];
+ SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+ Surface[] surfaces = new Surface[2];
+ OutputConfiguration[] outputConfigs = new OutputConfiguration[2];
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+
+ // Create surface textures with the same size
+ for (int i = 0; i < 2; i++) {
+ previewListener[i] = new CameraPreviewListener();
+ mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+ previewTexture[i] = getAvailableSurfaceTexture(
+ WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+ assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+ previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ // Correct the preview display rotation.
+ updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+ surfaces[i] = new Surface(previewTexture[i]);
+ outputConfigs[i] = new OutputConfiguration(surfaces[i]);
+ outputConfigs[i].enableSurfaceSharing();
+ outputConfigurations.add(outputConfigs[i]);
+ }
+
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+ boolean previewDone =
+ previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview", previewDone);
+
+ //try to dynamically add and remove any of the initial configured outputs
+ try {
+ outputConfigs[1].addSurface(surfaces[0]);
+ updateOutputConfiguration(cameraId, outputConfigs[1]);
+ fail("should get IllegalArgumentException due to invalid output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ outputConfigs[1].removeSurface(surfaces[0]);
+ }
+
+ try {
+ outputConfigs[1].removeSurface(surfaces[1]);
+ fail("should get IllegalArgumentException due to invalid output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+
+ try {
+ outputConfigs[0].addSurface(surfaces[1]);
+ updateOutputConfiguration(cameraId, outputConfigs[0]);
+ fail("should get IllegalArgumentException due to invalid output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ outputConfigs[0].removeSurface(surfaces[1]);
+ }
+
+ try {
+ outputConfigs[0].removeSurface(surfaces[0]);
+ fail("should get IllegalArgumentException due to invalid output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+
+ //Check that we are able to add a shared texture surface with different size
+ List<Size> orderedPreviewSizes = getOrderedPreviewSizes(cameraId);
+ Size textureSize = previewSize;
+ for (Size s : orderedPreviewSizes) {
+ if (!s.equals(previewSize)) {
+ textureSize = s;
+ break;
+ }
+ }
+ if (textureSize.equals(previewSize)) {
+ return;
+ }
+ SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID*/ 5);
+ outputTexture.setDefaultBufferSize(textureSize.getWidth(), textureSize.getHeight());
+ Surface outputSurface = new Surface(outputTexture);
+ //Add a valid output surface and then verify that it cannot be added any more
+ outputConfigs[1].addSurface(outputSurface);
+ updateOutputConfiguration(cameraId, outputConfigs[1]);
+ try {
+ outputConfigs[1].addSurface(outputSurface);
+ fail("should get IllegalStateException due to duplicate output");
+ } catch (IllegalStateException e) {
+ // expected exception
+ }
+
+ outputConfigs[0].addSurface(outputSurface);
+ try {
+ updateOutputConfiguration(cameraId, outputConfigs[0]);
+ fail("should get IllegalArgumentException due to duplicate output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ outputConfigs[0].removeSurface(outputSurface);
+ }
+
+ //Verify that the same surface cannot be removed twice
+ outputConfigs[1].removeSurface(outputSurface);
+ updateOutputConfiguration(cameraId, outputConfigs[1]);
+ try {
+ outputConfigs[0].removeSurface(outputSurface);
+ fail("should get IllegalArgumentException due to invalid output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+ try {
+ outputConfigs[1].removeSurface(outputSurface);
+ fail("should get IllegalArgumentException due to invalid output");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+
+ stopPreview(cameraId);
+ }
+
+ /*
+ * Verify dynamic shared surface behavior using multiple ImageReaders.
+ */
+ public void testSharedSurfaceImageReaderSwitch() throws Exception {
+ for (String cameraId : mCameraIds) {
+ try {
+ openCamera(cameraId);
+ if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+ Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+ continue;
+ }
+ if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + cameraId +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testSharedSurfaceImageReaderSwitch(cameraId, NUM_SURFACE_SWITCHES);
+ }
+ finally {
+ closeCamera(cameraId);
+ }
+ }
+ }
+
+ private void testSharedSurfaceImageReaderSwitch(String cameraId, int switchCount)
+ throws Exception {
+ if (OutputConfiguration.getMaxSharedSurfaceCount() < (IMG_READER_COUNT + 1)) {
+ return;
+ }
+
+ SimpleImageListener imageListeners[] = new SimpleImageListener[IMG_READER_COUNT];
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ ImageReader imageReaders[] = new ImageReader[IMG_READER_COUNT];
+ Surface readerSurfaces[] = new Surface[IMG_READER_COUNT];
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+ CameraPreviewListener previewListener = new CameraPreviewListener();
+ mTextureView[0].setSurfaceTextureListener(previewListener);
+ SurfaceTexture previewTexture = getAvailableSurfaceTexture(WAIT_FOR_COMMAND_TO_COMPLETE,
+ mTextureView[0]);
+ assertNotNull("Unable to get preview surface texture", previewTexture);
+ previewTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ updatePreviewDisplayRotation(previewSize, mTextureView[0]);
+ Surface previewSurface = new Surface(previewTexture);
+ OutputConfiguration outputConfig = new OutputConfiguration(previewSurface);
+ outputConfig.enableSurfaceSharing();
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+ outputConfigurations.add(outputConfig);
+
+ //Start regular preview streaming
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+ boolean previewDone = previewListener.waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview", previewDone);
+
+ //Test shared image reader outputs
+ for (int i = 0; i < IMG_READER_COUNT; i++) {
+ imageListeners[i] = new SimpleImageListener();
+ imageReaders[i] = ImageReader.newInstance(previewSize.getWidth(),
+ previewSize.getHeight(), ImageFormat.PRIVATE, 2);
+ imageReaders[i].setOnImageAvailableListener(imageListeners[i], mHandler);
+ readerSurfaces[i] = imageReaders[i].getSurface();
+ }
+
+ for (int j = 0; j < switchCount; j++) {
+ for (int i = 0; i < IMG_READER_COUNT; i++) {
+ outputConfig.addSurface(readerSurfaces[i]);
+ updateOutputConfiguration(cameraId, outputConfig);
+ CaptureRequest.Builder imageReaderRequestBuilder = getCaptureBuilder(cameraId,
+ CameraDevice.TEMPLATE_PREVIEW);
+ imageReaderRequestBuilder.addTarget(readerSurfaces[i]);
+ capture(cameraId, imageReaderRequestBuilder.build(), resultListener);
+ imageListeners[i].waitForAnyImageAvailable(PREVIEW_TIME_MS);
+ Image img = imageReaders[i].acquireLatestImage();
+ assertNotNull("Invalid image acquired!", img);
+ img.close();
+ outputConfig.removeSurface(readerSurfaces[i]);
+ updateOutputConfiguration(cameraId, outputConfig);
+ }
+ }
+
+ for (int i = 0; i < IMG_READER_COUNT; i++) {
+ imageReaders[i].close();
+ }
+
+ stopPreview(cameraId);
+ }
+
+ /*
+ * Test the dynamic shared surface limit.
+ */
+ public void testSharedSurfaceLimit() throws Exception {
+ for (String cameraId : mCameraIds) {
+ try {
+ openCamera(cameraId);
+ if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+ Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+ continue;
+ }
+ if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + cameraId +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testSharedSurfaceLimitByCamera(cameraId,
+ Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS);
+ }
+ finally {
+ closeCamera(cameraId);
+ }
+ }
+ }
+
+ private void testSharedSurfaceLimitByCamera(String cameraId, int surfaceLimit)
+ throws Exception {
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+ CameraPreviewListener[] previewListener = new CameraPreviewListener[surfaceLimit];
+ SurfaceTexture[] previewTexture = new SurfaceTexture[surfaceLimit];
+ Surface[] surfaces = new Surface[surfaceLimit];
+ int sequenceId = -1;
+
+ if ((surfaceLimit <= 1) ||
+ (surfaceLimit < OutputConfiguration.getMaxSharedSurfaceCount())) {
+ return;
+ }
+
+ // Create surface textures with the same size
+ for (int i = 0; i < surfaceLimit; i++) {
+ previewListener[i] = new CameraPreviewListener();
+ mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+ previewTexture[i] = getAvailableSurfaceTexture(
+ WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+ assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+ previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ // Correct the preview display rotation.
+ updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+ surfaces[i] = new Surface(previewTexture[i]);
+ }
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+ // Create shared outputs for the two surface textures
+ OutputConfiguration surfaceSharedOutput = new OutputConfiguration(surfaces[0]);
+ surfaceSharedOutput.enableSurfaceSharing();
+
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+ outputConfigurations.add(surfaceSharedOutput);
+
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+ boolean previewDone =
+ previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview", previewDone);
+ mTextureView[0].setSurfaceTextureListener(null);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+
+ int i = 1;
+ for (; i < surfaceLimit; i++) {
+ //Add one more output surface while preview is streaming
+ if (i >= OutputConfiguration.getMaxSharedSurfaceCount()){
+ try {
+ surfaceSharedOutput.addSurface(surfaces[i]);
+ fail("should get IllegalArgumentException due to output surface limit");
+ } catch (IllegalArgumentException e) {
+ //expected
+ break;
+ }
+ } else {
+ surfaceSharedOutput.addSurface(surfaces[i]);
+ updateOutputConfiguration(cameraId, surfaceSharedOutput);
+ sequenceId = updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+ }
+ }
+
+ for (; i > 0; i--) {
+ if (i >= OutputConfiguration.getMaxSharedSurfaceCount()) {
+ try {
+ surfaceSharedOutput.removeSurface(surfaces[i]);
+ fail("should get IllegalArgumentException due to output surface limit");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+ } else {
+ surfaceSharedOutput.removeSurface(surfaces[i]);
+
+ }
+ }
+ //Remove all previously added shared outputs in one call
+ updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+ long lastSequenceFrameNumber = resultListener.getCaptureSequenceLastFrameNumber(
+ sequenceId, PREVIEW_TIME_MS);
+ checkForLastFrameInSequence(lastSequenceFrameNumber, resultListener);
+ updateOutputConfiguration(cameraId, surfaceSharedOutput);
+ SystemClock.sleep(PREVIEW_TIME_MS);
+
+ stopPreview(cameraId);
+ }
+
+ /*
+ * Test dynamic shared surface switch behavior.
+ */
+ public void testSharedSurfaceSwitch() throws Exception {
+ for (String cameraId : mCameraIds) {
+ try {
+ openCamera(cameraId);
+ if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
+ Log.i(TAG, "Camera " + cameraId + " is legacy, skipping");
+ continue;
+ }
+ if (!getStaticInfo(cameraId).isColorOutputSupported()) {
+ Log.i(TAG, "Camera " + cameraId +
+ " does not support color outputs, skipping");
+ continue;
+ }
+
+ testSharedSurfaceSwitchByCamera(cameraId, NUM_SURFACE_SWITCHES);
+ }
+ finally {
+ closeCamera(cameraId);
+ }
+ }
+ }
+
+ private void testSharedSurfaceSwitchByCamera(String cameraId, int switchCount)
+ throws Exception {
+ Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
+ CameraPreviewListener[] previewListener = new CameraPreviewListener[2];
+ SurfaceTexture[] previewTexture = new SurfaceTexture[2];
+ Surface[] surfaces = new Surface[2];
+
+ // Create surface textures with the same size
+ for (int i = 0; i < 2; i++) {
+ previewListener[i] = new CameraPreviewListener();
+ mTextureView[i].setSurfaceTextureListener(previewListener[i]);
+ previewTexture[i] = getAvailableSurfaceTexture(
+ WAIT_FOR_COMMAND_TO_COMPLETE, mTextureView[i]);
+ assertNotNull("Unable to get preview surface texture", previewTexture[i]);
+ previewTexture[i].setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ // Correct the preview display rotation.
+ updatePreviewDisplayRotation(previewSize, mTextureView[i]);
+ surfaces[i] = new Surface(previewTexture[i]);
+ }
+
+ SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+ // Create shared outputs for the two surface textures
+ OutputConfiguration surfaceSharedOutput = new OutputConfiguration(surfaces[0]);
+ surfaceSharedOutput.enableSurfaceSharing();
+
+ List<OutputConfiguration> outputConfigurations = new ArrayList<>();
+ outputConfigurations.add(surfaceSharedOutput);
+
+ startPreviewWithConfigs(cameraId, outputConfigurations, null);
+
+ boolean previewDone =
+ previewListener[0].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
+ assertTrue("Unable to start preview", previewDone);
+ mTextureView[0].setSurfaceTextureListener(null);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+
+ for (int i = 0; i < switchCount; i++) {
+ //Add one more output surface while preview is streaming
+ surfaceSharedOutput.addSurface(surfaces[1]);
+ updateOutputConfiguration(cameraId, surfaceSharedOutput);
+ int sequenceId = updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+
+ //Try to remove the shared surface while while we still have active requests that
+ //use it as output.
+ surfaceSharedOutput.removeSurface(surfaces[1]);
+ try {
+ updateOutputConfiguration(cameraId, surfaceSharedOutput);
+ fail("should get IllegalArgumentException due to pending requests");
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+
+ //Wait for all pending requests to arrive and remove the shared output during active
+ //streaming
+ updateRepeatingRequest(cameraId, outputConfigurations, resultListener);
+ long lastSequenceFrameNumber = resultListener.getCaptureSequenceLastFrameNumber(
+ sequenceId, PREVIEW_TIME_MS);
+ checkForLastFrameInSequence(lastSequenceFrameNumber, resultListener);
+ updateOutputConfiguration(cameraId, surfaceSharedOutput);
+
+ SystemClock.sleep(PREVIEW_TIME_MS);
+ }
+
+ stopPreview(cameraId);
+ }
+
+ private void checkForLastFrameInSequence(long lastSequenceFrameNumber,
+ SimpleCaptureCallback listener) throws Exception {
+ // Find the last frame number received in results and failures.
+ long lastFrameNumber = -1;
+ while (listener.hasMoreResults()) {
+ TotalCaptureResult result = listener.getTotalCaptureResult(PREVIEW_TIME_MS);
+ if (lastFrameNumber < result.getFrameNumber()) {
+ lastFrameNumber = result.getFrameNumber();
+ }
+ }
+
+ while (listener.hasMoreFailures()) {
+ ArrayList<CaptureFailure> failures = listener.getCaptureFailures(
+ /*maxNumFailures*/ 1);
+ for (CaptureFailure failure : failures) {
+ if (lastFrameNumber < failure.getFrameNumber()) {
+ lastFrameNumber = failure.getFrameNumber();
+ }
+ }
+ }
+
+ // Verify the last frame number received from capture sequence completed matches the
+ // the last frame number of the results and failures.
+ assertEquals(String.format("Last frame number from onCaptureSequenceCompleted " +
+ "(%d) doesn't match the last frame number received from " +
+ "results/failures (%d)", lastSequenceFrameNumber, lastFrameNumber),
+ lastSequenceFrameNumber, lastFrameNumber);
+ }
+
+ /*
* Verify behavior of sharing surfaces within one OutputConfiguration
*/
public void testSharedSurfaces() throws Exception {
@@ -517,7 +992,7 @@
// Run preview with both surfaces, and verify at least one frame is received for each
// surface.
- updateOutputConfigs(cameraId, deferredConfigs, null);
+ finalizeOutputConfigs(cameraId, deferredConfigs, null);
previewDone =
previewListener[1].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
assertTrue("Unable to start preview 1", previewDone);
@@ -547,7 +1022,7 @@
surfaceSharedOutput.addSurface(surfaces[1]);
deferredConfigs.clear();
deferredConfigs.add(surfaceSharedOutput);
- updateOutputConfigs(cameraId, deferredConfigs, null);
+ finalizeOutputConfigs(cameraId, deferredConfigs, null);
previewDone =
previewListener[1].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
assertTrue("Unable to start preview 1", previewDone);
@@ -574,7 +1049,7 @@
surfaceSharedOutput.addSurface(surfaces[1]);
deferredConfigs.clear();
deferredConfigs.add(surfaceSharedOutput);
- updateOutputConfigs(cameraId, deferredConfigs, null);
+ finalizeOutputConfigs(cameraId, deferredConfigs, null);
for (int i = 0; i < 2; i++) {
previewDone =
previewListener[i].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
@@ -584,4 +1059,20 @@
SystemClock.sleep(PREVIEW_TIME_MS);
stopPreview(cameraId);
}
+
+ private final class SimpleImageListener implements ImageReader.OnImageAvailableListener {
+ private final ConditionVariable imageAvailable = new ConditionVariable();
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ imageAvailable.open();
+ }
+
+ public void waitForAnyImageAvailable(long timeout) {
+ if (imageAvailable.block(timeout)) {
+ imageAvailable.close();
+ } else {
+ fail("wait for image available timed out after " + timeout + "ms");
+ }
+ }
+ }
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
index e2a61a6..06158d8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
@@ -16,6 +16,7 @@
package android.hardware.camera2.cts;
+import android.graphics.SurfaceTexture;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
import android.util.Log;
import android.util.Size;
@@ -59,8 +60,29 @@
testCameraDeviceSimplePreviewNative(mPreviewSurface));
}
+ public void testCameraDevicePreviewWithSessionParameters() {
+ // Init preview surface to a guaranteed working size
+ updatePreviewSurface(new Size(640, 480));
+ assertTrue("testCameraDevicePreviewWithSessionParametersNative fail, see log for details",
+ testCameraDevicePreviewWithSessionParametersNative(mPreviewSurface));
+ }
+
+ public void testCameraDeviceSharedOutputUpdate() {
+ // Init preview surface to a guaranteed working size
+ Size previewSize = new Size(640, 480);
+ updatePreviewSurface(previewSize);
+ SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID*/ 5);
+ outputTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ Surface outputSurface = new Surface(outputTexture);
+ assertTrue("testCameraDeviceSharedWindowAddRemove fail, see log for details",
+ testCameraDeviceSharedOutputUpdate(mPreviewSurface, outputSurface));
+ }
+
private static native boolean testCameraDeviceOpenAndCloseNative();
private static native boolean testCameraDeviceCreateCaptureRequestNative();
private static native boolean testCameraDeviceSessionOpenAndCloseNative(Surface preview);
private static native boolean testCameraDeviceSimplePreviewNative(Surface preview);
+ private static native boolean testCameraDevicePreviewWithSessionParametersNative(
+ Surface preview);
+ private static native boolean testCameraDeviceSharedOutputUpdate(Surface src, Surface dst);
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 1e9696f..6f5ccfb 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -65,6 +65,7 @@
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
private static final int RECORDING_DURATION_MS = 3000;
+ private static final int PREVIEW_DURATION_MS = 3000;
private static final float DURATION_MARGIN = 0.2f;
private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
private static final float FRAMEDURATION_MARGIN = 0.2f;
@@ -295,7 +296,8 @@
}
public void testConstrainedHighSpeedRecording() throws Exception {
- constrainedHighSpeedRecording();
+ constrainedHighSpeedRecording(/*enableSessionParams*/ false);
+ constrainedHighSpeedRecording(/*enableSessionParams*/ true);
}
/**
@@ -495,7 +497,8 @@
// Start recording
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate,
- fpsRange, resultListener, /*useHighSpeedSession*/false);
+ fpsRange, resultListener, /*useHighSpeedSession*/false,
+ /*enableHighSpeedParams*/ false);
// Record certain duration.
SystemClock.sleep(RECORDING_DURATION_MS);
@@ -517,7 +520,7 @@
}
}
- private void constrainedHighSpeedRecording() throws Exception {
+ private void constrainedHighSpeedRecording(boolean enableSessionParams) throws Exception {
for (String id : mCameraIds) {
try {
Log.i(TAG, "Testing constrained high speed recording for camera " + id);
@@ -550,31 +553,47 @@
continue;
}
+ SimpleCaptureCallback previewResultListener = new SimpleCaptureCallback();
+
+ // prepare preview surface by using video size.
+ updatePreviewSurfaceWithVideo(size, captureRate);
+
+ startConstrainedPreview(fpsRange, previewResultListener);
+
mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate +
"fps_" + id + "_" + size.toString() + ".mp4";
prepareRecording(size, VIDEO_FRAME_RATE, captureRate);
- // prepare preview surface by using video size.
- updatePreviewSurfaceWithVideo(size, captureRate);
+ SystemClock.sleep(PREVIEW_DURATION_MS);
- // Start recording
+ stopCameraStreaming();
+
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+ // Start recording
startSlowMotionRecording(/*useMediaRecorder*/true, VIDEO_FRAME_RATE,
captureRate, fpsRange, resultListener,
- /*useHighSpeedSession*/true);
+ /*useHighSpeedSession*/true,
+ /*enableHighSpeedParams*/ enableSessionParams);
// Record certain duration.
SystemClock.sleep(RECORDING_DURATION_MS);
// Stop recording and preview
stopRecording(/*useMediaRecorder*/true);
+
+ startConstrainedPreview(fpsRange, previewResultListener);
+
// Convert number of frames camera produced into the duration in unit of ms.
float frameDurationMs = 1000.0f / VIDEO_FRAME_RATE;
float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
// Validation.
validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
+
+ SystemClock.sleep(PREVIEW_DURATION_MS);
+
+ stopCameraStreaming();
}
}
@@ -635,9 +654,30 @@
return fixedRanges;
}
+ private void startConstrainedPreview(Range<Integer> fpsRange,
+ CameraCaptureSession.CaptureCallback listener) throws Exception {
+ List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+ assertTrue("Preview surface should be valid", mPreviewSurface.isValid());
+ outputSurfaces.add(mPreviewSurface);
+ mSessionListener = new BlockingSessionCallback();
+ mSession = configureCameraSession(mCamera, outputSurfaces, /*isHighSpeed*/ true,
+ mSessionListener, mHandler);
+
+ List<CaptureRequest> slowMoRequests = null;
+ CaptureRequest.Builder requestBuilder =
+ mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
+ requestBuilder.addTarget(mPreviewSurface);
+ slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession).
+ createHighSpeedRequestList(requestBuilder.build());
+
+ mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
+ }
+
private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
int captureRate, Range<Integer> fpsRange,
- CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession) throws Exception {
+ CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession,
+ boolean enableHighSpeedParams) throws Exception {
List<Surface> outputSurfaces = new ArrayList<Surface>(2);
assertTrue("Both preview and recording surfaces should be valid",
mPreviewSurface.isValid() && mRecordingSurface.isValid());
@@ -648,8 +688,13 @@
outputSurfaces.add(mReaderSurface);
}
mSessionListener = new BlockingSessionCallback();
- mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
- mSessionListener, mHandler);
+ if (useHighSpeedSession && enableHighSpeedParams) {
+ mSession = buildConstrainedCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
+ mSessionListener, mHandler);
+ } else {
+ mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
+ mSessionListener, mHandler);
+ }
// Create slow motion request list
List<CaptureRequest> slowMoRequests = null;
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index a900b84..54405a4 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -146,6 +146,7 @@
assertTrue("Camera does not contain outputted image resolution " + actualSize,
testSizes.contains(actualSize));
+ imageReader.close();
} finally {
closeDevice(id);
}
@@ -957,6 +958,65 @@
}
}
+ public void testAfSceneChange() throws Exception {
+ final int NUM_FRAMES_VERIFIED = 3;
+
+ for (String id : mCameraIds) {
+ Log.i(TAG, String.format("Testing Camera %s for AF scene change", id));
+
+ StaticMetadata staticInfo =
+ new StaticMetadata(mCameraManager.getCameraCharacteristics(id));
+ if (!staticInfo.isAfSceneChangeSupported()) {
+ continue;
+ }
+
+ openDevice(id);
+
+ try {
+ SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1);
+ Surface previewSurface = new Surface(preview);
+
+ CaptureRequest.Builder previewRequest = preparePreviewTestSession(preview);
+ SimpleCaptureCallback previewListener = new CameraTestUtils.SimpleCaptureCallback();
+
+ int[] availableAfModes = mStaticInfo.getAfAvailableModesChecked();
+
+ // Test AF scene change in each AF mode.
+ for (int afMode : availableAfModes) {
+ previewRequest.set(CaptureRequest.CONTROL_AF_MODE, afMode);
+
+ int sequenceId = mCameraSession.setRepeatingRequest(previewRequest.build(),
+ previewListener, mHandler);
+
+ // Verify that AF scene change is NOT_DETECTED or DETECTED in CONTINUOUS_VIDEO
+ // and CONTINUOUS_PICTURE AF modes, and NOT_DETECTED in other AF modes.
+ for (int i = 0; i < NUM_FRAMES_VERIFIED; i++) {
+ TotalCaptureResult result =
+ previewListener.getTotalCaptureResult(CAPTURE_TIMEOUT);
+ if (afMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO ||
+ afMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
+ mCollector.expectKeyValueIsIn(result,
+ CaptureResult.CONTROL_AF_SCENE_CHANGE,
+ CaptureResult.CONTROL_AF_SCENE_CHANGE_DETECTED,
+ CaptureResult.CONTROL_AF_SCENE_CHANGE_NOT_DETECTED);
+ } else {
+ mCollector.expectKeyValueEquals(result,
+ CaptureResult.CONTROL_AF_SCENE_CHANGE,
+ CaptureResult.CONTROL_AF_SCENE_CHANGE_NOT_DETECTED);
+ }
+ }
+
+ mCameraSession.stopRepeating();
+ previewListener.getCaptureSequenceLastFrameNumber(sequenceId, CAPTURE_TIMEOUT);
+ previewListener.drain();
+ }
+ } finally {
+ closeDevice(id);
+ }
+ }
+ }
+
+
private CaptureRequest.Builder preparePreviewTestSession(SurfaceTexture preview)
throws Exception {
Surface previewSurface = new Surface(preview);
diff --git a/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java b/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java
index b89f9bb..98f1032 100644
--- a/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java
+++ b/tests/camera/src/android/hardware/camera2/cts/rs/AllocationCache.java
@@ -223,7 +223,7 @@
if (other instanceof AllocationKey){
AllocationKey otherKey = (AllocationKey) other;
- return otherKey.mType.equals(mType) && otherKey.mUsage == otherKey.mUsage;
+ return otherKey.mType.equals(mType) && otherKey.mUsage == mUsage;
}
return false;
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index a1d63d8..66324da 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -64,10 +64,10 @@
private static final String TAG = "MultiViewTestCase";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
- protected TextureView[] mTextureView = new TextureView[2];
+ protected TextureView[] mTextureView =
+ new TextureView[Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS];
protected String[] mCameraIds;
protected Handler mHandler;
@@ -99,8 +99,9 @@
mHandler = new Handler(mHandlerThread.getLooper());
mCameraListener = new BlockingStateCallback();
Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mContext;
- mTextureView[0] = activity.getTextureView(0);
- mTextureView[1] = activity.getTextureView(1);
+ for (int i = 0; i < Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS; i++) {
+ mTextureView[i] = activity.getTextureView(i);
+ }
assertNotNull("Unable to get texture view", mTextureView);
mCameraIdMap = new HashMap<String, Integer>();
int numCameras = mCameraIds.length;
@@ -246,13 +247,13 @@
camera.startPreview(outputSurfaces, listener);
}
- protected void startPreviewWithConfigs(String cameraId,
+ protected int startPreviewWithConfigs(String cameraId,
List<OutputConfiguration> outputConfigs,
CaptureCallback listener)
throws Exception {
CameraHolder camera = getCameraHolder(cameraId);
assertTrue("Camera " + cameraId + " is not openned", camera.isOpened());
- camera.startPreviewWithConfigs(outputConfigs, listener);
+ return camera.startPreviewWithConfigs(outputConfigs, listener);
}
protected void stopPreview(String cameraId) throws Exception {
@@ -261,11 +262,39 @@
camera.stopPreview();
}
- protected void updateOutputConfigs(String cameraId, List<OutputConfiguration> configs,
+ protected void finalizeOutputConfigs(String cameraId, List<OutputConfiguration> configs,
CaptureCallback listener) throws Exception {
CameraHolder camera = getCameraHolder(cameraId);
assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
- camera.updateOutputConfigs(configs, listener);
+ camera.finalizeOutputConfigs(configs, listener);
+ }
+
+ protected int updateRepeatingRequest(String cameraId, List<OutputConfiguration> configs,
+ CaptureCallback listener) throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+ return camera.updateRepeatingRequest(configs, listener);
+ }
+
+ protected void updateOutputConfiguration(String cameraId, OutputConfiguration config)
+ throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+ camera.updateOutputConfiguration(config);
+ }
+
+ protected void capture(String cameraId, CaptureRequest request, CaptureCallback listener)
+ throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+ camera.capture(request, listener);
+ }
+
+ protected CaptureRequest.Builder getCaptureBuilder(String cameraId, int templateId)
+ throws Exception {
+ CameraHolder camera = getCameraHolder(cameraId);
+ assertTrue("Camera " + cameraId + " is not opened", camera.isOpened());
+ return camera.getCaptureBuilder(templateId);
}
protected StaticMetadata getStaticInfo(String cameraId) {
@@ -426,6 +455,9 @@
throws Exception {
mSessionListener = new BlockingSessionCallback();
mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+ if (outputSurfaces.isEmpty()) {
+ return;
+ }
// TODO: vary the different settings like crop region to cover more cases.
CaptureRequest.Builder captureBuilder =
@@ -457,7 +489,7 @@
state == BlockingSessionCallback.SESSION_CONFIGURE_FAILED);
}
- public void startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
+ public int startPreviewWithConfigs(List<OutputConfiguration> outputConfigs,
CaptureCallback listener)
throws Exception {
createSessionWithConfigs(outputConfigs);
@@ -470,13 +502,11 @@
captureBuilder.addTarget(surface);
}
}
- mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+ return mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
}
- public void updateOutputConfigs(List<OutputConfiguration> configs,
+ public int updateRepeatingRequest(List<OutputConfiguration> configs,
CaptureCallback listener) throws Exception {
- mSession.finalizeOutputConfigurations(configs);
-
CaptureRequest.Builder captureBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -485,7 +515,26 @@
captureBuilder.addTarget(surface);
}
}
- mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+ return mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+ }
+
+ public void updateOutputConfiguration(OutputConfiguration config) throws Exception {
+ mSession.updateOutputConfiguration(config);
+ }
+
+ public void capture(CaptureRequest request, CaptureCallback listener)
+ throws Exception {
+ mSession.capture(request, listener, mHandler);
+ }
+
+ public CaptureRequest.Builder getCaptureBuilder(int templateId) throws Exception {
+ return mCamera.createCaptureRequest(templateId);
+ }
+
+ public void finalizeOutputConfigs(List<OutputConfiguration> configs,
+ CaptureCallback listener) throws Exception {
+ mSession.finalizeOutputConfigurations(configs);
+ updateRepeatingRequest(configs, listener);
}
public boolean isPreviewStarted() {
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index 3b697ce..f5fff8e 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -462,6 +462,30 @@
}
@UiThreadTest
+ public void testStabilizationOneShotPreviewCallback() throws Exception {
+ int nCameras = Camera.getNumberOfCameras();
+ for (int id = 0; id < nCameras; id++) {
+ Log.v(TAG, "Camera id=" + id);
+ testStabilizationOneShotPreviewCallbackByCamera(id);
+ }
+ }
+
+ private void testStabilizationOneShotPreviewCallbackByCamera(int cameraId) throws Exception {
+ initializeMessageLooper(cameraId);
+ Parameters params = mCamera.getParameters();
+ if(!params.isVideoStabilizationSupported()) {
+ return;
+ }
+ //Check whether we can support preview callbacks along with stabilization
+ params.setVideoStabilization(true);
+ mCamera.setParameters(params);
+ mCamera.setOneShotPreviewCallback(mPreviewCallback);
+ checkPreviewCallback();
+ terminateMessageLooper();
+ assertEquals(PREVIEW_CALLBACK_RECEIVED, mPreviewCallbackResult);
+ }
+
+ @UiThreadTest
public void testSetOneShotPreviewCallback() throws Exception {
int nCameras = Camera.getNumberOfCameras();
for (int id = 0; id < nCameras; id++) {
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
index 834d37c..0f48364 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
@@ -227,18 +227,15 @@
verify(spyStateCb, times(1)).onOpened(any(CameraDevice.class));
// Verify that we can no longer open the camera, as it is held by a higher priority process
- boolean openException = false;
try {
manager.openCamera(chosenCamera, spyStateCb, cameraHandler);
+ fail("Didn't receive exception when trying to open camera held by higher priority " +
+ "process.");
} catch(CameraAccessException e) {
assertTrue("Received incorrect camera exception when opening camera: " + e,
e.getReason() == CameraAccessException.CAMERA_IN_USE);
- openException = true;
}
- assertTrue("Didn't receive exception when trying to open camera held by higher priority " +
- "process.", openException);
-
// Verify that attempting to open the camera didn't cause anything weird to happen in the
// other process.
List<ErrorLoggingService.LogEvent> eventList2 = null;
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index aad5fd8..b592709 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -37,6 +37,7 @@
import android.hardware.cts.helpers.CameraUtils;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.location.Location;
import android.location.LocationManager;
@@ -780,6 +781,44 @@
}
/**
+ * Build a new constrained camera session with output surfaces, type and recording session
+ * parameters.
+ *
+ * @param camera The CameraDevice to be configured.
+ * @param outputSurfaces The surface list that used for camera output.
+ * @param listener The callback CameraDevice will notify when capture results are available.
+ */
+ public static CameraCaptureSession buildConstrainedCameraSession(CameraDevice camera,
+ List<Surface> outputSurfaces, boolean isHighSpeed,
+ CameraCaptureSession.StateCallback listener, Handler handler)
+ throws CameraAccessException {
+ BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
+
+ CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ CaptureRequest recordSessionParams = builder.build();
+
+ List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
+ for (Surface surface : outputSurfaces) {
+ outConfigurations.add(new OutputConfiguration(surface));
+ }
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_HIGH_SPEED, outConfigurations, sessionListener,
+ handler);
+ sessionConfig.setSessionParameters(recordSessionParams);
+ camera.createCaptureSession(sessionConfig);
+
+ CameraCaptureSession session =
+ sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ assertFalse("Camera session should not be a reprocessable session",
+ session.isReprocessable());
+ assertTrue("Capture session type must be High Speed",
+ CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
+ session.getClass()));
+
+ return session;
+ }
+
+ /**
* Configure a new camera session with output configurations.
*
* @param camera The CameraDevice to be configured.
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
index aa048cf..5563700 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
@@ -877,6 +877,24 @@
}
/**
+ * Check if the key is non-null, and the key value is one of the expected values.
+ *
+ * @param result {@link CaptureResult} to get the key from.
+ * @param key The {@link CaptureResult} key to be checked.
+ * @param expected The expected values of the CaptureResult key.
+ */
+ public <T> void expectKeyValueIsIn(CaptureResult result,
+ CaptureResult.Key<T> key, T... expected) {
+ T value = expectKeyValueNotNull(result, key);
+ if (value == null) {
+ return;
+ }
+ String reason = "Key " + key.getName() + " value " + value
+ + " isn't one of the expected values " + Arrays.deepToString(expected);
+ expectContains(reason, expected, value);
+ }
+
+ /**
* Check if the key is non-null, and the key value contains the expected element.
*
* @param characteristics {@link CameraCharacteristics} to check.
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 319ec91..3c551f8 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -2258,6 +2258,13 @@
}
/**
+ * Check if AF scene change key is supported.
+ */
+ public boolean isAfSceneChangeSupported() {
+ return areKeysAvailable(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+ }
+
+ /**
* Get the value in index for a fixed-size array from a given key.
*
* <p>If the camera device is incorrectly reporting values, log a warning and return
diff --git a/tests/core/Android.mk b/tests/core/Android.mk
index 7a2a708..e9c9793 100644
--- a/tests/core/Android.mk
+++ b/tests/core/Android.mk
@@ -14,6 +14,4 @@
LOCAL_PATH:= $(call my-dir)
-BUILD_CTSCORE_PACKAGE:=$(LOCAL_PATH)/ctscore.mk
-
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/core/ctscore.mk b/tests/core/ctscore.mk
deleted file mode 100644
index 10a91f1..0000000
--- a/tests/core/ctscore.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2009 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_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-# don't include these packages in any target
-LOCAL_MODULE_TAGS := optional
-# and when installed explicitly put them in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# Don't delete META-INF from the core-tests jar
-LOCAL_DONT_DELETE_JAR_META_INF := true
-
-# TODO: Clean up this mess. (b/26483949). libnativehelper_compat_libc++ pulls in its own
-# static copy of libc++ and the libc++ we're bundling here is the platform libc++. This is
-# bround to break but is being submitted as a workaround for failing CTS tests.
-LOCAL_JNI_SHARED_LIBRARIES := libjavacoretests libsqlite_jni libnativehelper_compat_libc++ libc++
-
-# Include both the 32 and 64 bit versions of libjavacoretests,
-# where applicable.
-LOCAL_MULTILIB := both
-
-include $(BUILD_PACKAGE)
diff --git a/tests/core/runner/Android.mk b/tests/core/runner/Android.mk
index 7e4fb0d..f86ee2f 100644
--- a/tests/core/runner/Android.mk
+++ b/tests/core/runner/Android.mk
@@ -14,21 +14,6 @@
LOCAL_PATH:= $(call my-dir)
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
- $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-include $(CLEAR_VARS)
-
-# include this package in the tests target for continuous testing
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_PACKAGE_NAME := android.core.tests.runner
-
-LOCAL_STATIC_JAVA_LIBRARIES := cts-test-runner
-
-include $(BUILD_CTSCORE_PACKAGE)
-
#==========================================================
# Build the core runner.
#==========================================================
diff --git a/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java b/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java
deleted file mode 100644
index 108e8fd..0000000
--- a/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * This file is a copy of https://cs.corp.google.com/android/frameworks/testing/runner/src/main/java/android/support/test/internal/runner/TestLoader.java
- * The only changes that have been made starts with // Libcore-specific
- */
-
-package com.android.cts.core.internal.runner;
-
-import android.util.Log;
-
-import org.junit.runner.Description;
-import org.junit.runner.notification.Failure;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A class for loading JUnit3 and JUnit4 test classes given a set of potential class names.
- */
-public final class TestLoader {
-
- private static final String LOG_TAG = "TestLoader";
- // Libcore-specific change: Fully qualified name of TestNG annotation class.
- private static final String TESTNG_TEST = "org.testng.annotations.Test";
-
- private Map<String, Class<?>> mLoadedClassesMap = new LinkedHashMap<String, Class<?>>();
- private Map<String, Failure> mLoadFailuresMap = new LinkedHashMap<String, Failure>();
-
- private ClassLoader mClassLoader;
-
- /**
- * Set the {@link ClassLoader} to be used to load test cases.
- *
- * @param loader {@link ClassLoader} to load test cases with.
- */
- public void setClassLoader(ClassLoader loader) {
- mClassLoader = loader;
- }
-
- /**
- * Loads the test class from a given class name if its not already loaded.
- * <p/>
- * Will store the result internally. Successfully loaded classes can be retrieved via
- * {@link #getLoadedClasses()}, failures via {@link #getLoadFailures()}.
- *
- * @param className the class name to attempt to load
- * @return the loaded class or null.
- */
- public Class<?> loadClass(String className) {
- Class<?> loadedClass = doLoadClass(className);
- if (loadedClass != null) {
- mLoadedClassesMap.put(className, loadedClass);
- }
- return loadedClass;
- }
-
- protected ClassLoader getClassLoader() {
- if (mClassLoader != null) {
- return mClassLoader;
- }
-
- // TODO: InstrumentationTestRunner uses
- // Class.forName(className, false, getTargetContext().getClassLoader());
- // Evaluate if that is needed. Initial testing indicates
- // getTargetContext().getClassLoader() == this.getClass().getClassLoader()
- return this.getClass().getClassLoader();
- }
-
- private Class<?> doLoadClass(String className) {
- if (mLoadFailuresMap.containsKey(className)) {
- // Don't load classes that already failed to load
- return null;
- } else if (mLoadedClassesMap.containsKey(className)) {
- // Class with the same name was already loaded, return it
- return mLoadedClassesMap.get(className);
- }
-
- try {
- ClassLoader myClassLoader = getClassLoader();
- return Class.forName(className, false, myClassLoader);
- } catch (ClassNotFoundException e) {
- String errMsg = String.format("Could not find class: %s", className);
- Log.e(LOG_TAG, errMsg);
- Description description = Description.createSuiteDescription(className);
- Failure failure = new Failure(description, e);
- mLoadFailuresMap.put(className, failure);
- }
- return null;
- }
-
- /**
- * Loads the test class from the given class name.
- * <p/>
- * Similar to {@link #loadClass(String)}, but will ignore classes that are
- * not tests.
- *
- * @param className the class name to attempt to load
- * @return the loaded class or null.
- */
- public Class<?> loadIfTest(String className) {
- Class<?> loadedClass = doLoadClass(className);
- if (loadedClass != null && isTestClass(loadedClass)) {
- mLoadedClassesMap.put(className, loadedClass);
- return loadedClass;
- }
- return null;
- }
-
- /**
- * @return whether this {@link TestLoader} contains any loaded classes or load failures.
- */
- public boolean isEmpty() {
- return mLoadedClassesMap.isEmpty() && mLoadFailuresMap.isEmpty();
- }
-
- /**
- * Get the {@link Collection) of classes successfully loaded via
- * {@link #loadIfTest(String)} calls.
- */
- public Collection<Class<?>> getLoadedClasses() {
- return mLoadedClassesMap.values();
- }
-
- /**
- * Get the {@link List) of {@link Failure} that occurred during
- * {@link #loadIfTest(String)} calls.
- */
- public Collection<Failure> getLoadFailures() {
- return mLoadFailuresMap.values();
- }
-
- /**
- * Determines if given class is a valid test class.
- *
- * @param loadedClass
- * @return <code>true</code> if loadedClass is a test
- */
- private boolean isTestClass(Class<?> loadedClass) {
- try {
- if (Modifier.isAbstract(loadedClass.getModifiers())) {
- logDebug(String.format("Skipping abstract class %s: not a test",
- loadedClass.getName()));
- return false;
- }
- // Libcore-specific change: Also consider TestNG annotated classes.
- if (isTestNgTestClass(loadedClass)) {
- return true;
- }
- // TODO: try to find upstream junit calls to replace these checks
- if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
- // ensure that if a TestCase, it has at least one test method otherwise
- // TestSuite will throw error
- if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
- return hasJUnit3TestMethod(loadedClass);
- }
- return true;
- }
- // TODO: look for a 'suite' method?
- if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
- return true;
- }
- for (Method testMethod : loadedClass.getMethods()) {
- if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
- return true;
- }
- }
- logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
- return false;
- } catch (Exception e) {
- // Defensively catch exceptions - Will throw runtime exception if it cannot load methods.
- // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
- // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
- // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
- // generic catch all.
- // For ICS+, Dalvik will throw a NoClassDefFoundException.
- Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
- loadedClass.getName()));
- return false;
- } catch (Error e) {
- // defensively catch Errors too
- Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
- loadedClass.getName()));
- return false;
- }
- }
-
- private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
- for (Method testMethod : loadedClass.getMethods()) {
- if (isPublicTestMethod(testMethod)) {
- return true;
- }
- }
- return false;
- }
-
- // copied from junit.framework.TestSuite
- private boolean isPublicTestMethod(Method m) {
- return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
- }
-
- // copied from junit.framework.TestSuite
- private boolean isTestMethod(Method m) {
- return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
- && m.getReturnType().equals(Void.TYPE);
- }
-
- // Libcore-specific change: Add method for checking TestNG-annotated classes.
- private static boolean isTestNgTestClass(Class<?> cls) {
- // TestNG test is either marked @Test at the class
- for (Annotation a : cls.getAnnotations()) {
- if (a.annotationType().getName().equals(TESTNG_TEST)) {
- return true;
- }
- }
-
- // Or It's marked @Test at the method level
- for (Method m : cls.getDeclaredMethods()) {
- for (Annotation a : m.getAnnotations()) {
- if (a.annotationType().getName().equals(TESTNG_TEST)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
-
- /**
- * Utility method for logging debug messages. Only actually logs a message if LOG_TAG is marked
- * as loggable to limit log spam during normal use.
- */
- private void logDebug(String msg) {
- if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
- Log.d(LOG_TAG, msg);
- }
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java b/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
deleted file mode 100644
index 4b18cdc..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner;
-
-import android.support.test.runner.AndroidJUnitRunner;
-
-/**
- * Constants used to communicate to and from {@link AndroidJUnitRunner}.
- */
-public interface AndroidJUnitRunnerConstants {
-
- /**
- * The name of the file containing the names of the tests to run.
- *
- * <p>This is an internal constant used within
- * {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
- * and
- * client side. The constant is used when there are too many test names to pass on the command
- * line, in which case they are stored in a file that is pushed to the device and then the
- * location of that file is passed in this argument. The {@code RunnerArgs} on the client will
- * read the contents of that file in order to retrieve the list of names and then return that
- * to
- * its client without the client being aware of how that was done.
- */
- String ARGUMENT_TEST_FILE = "testFile";
-
- /**
- * The name of the file containing the names of the tests not to run.
- *
- * <p>This is an internal constant used within
- * {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
- * and
- * client side. The constant is used when there are too many test names to pass on the command
- * line, in which case they are stored in a file that is pushed to the device and then the
- * location of that file is passed in this argument. The {@code RunnerArgs} on the client will
- * read the contents of that file in order to retrieve the list of names and then return that
- * to
- * its client without the client being aware of how that was done.
- */
- String ARGUMENT_NOT_TEST_FILE = "notTestFile";
-
- /**
- * A comma separated list of the names of test classes to run.
- *
- * <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
- * available
- * through the public API.
- */
- String ARGUMENT_TEST_CLASS = "class";
-
- /**
- * A comma separated list of the names of test classes not to run
- */
- String ARGUMENT_NOT_TEST_CLASS = "notClass";
-
- /**
- * A comma separated list of the names of test packages to run.
- *
- * <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
- * available
- * through the public API.
- */
- String ARGUMENT_TEST_PACKAGE = "package";
-
- /**
- * A comma separated list of the names of test packages not to run.
- */
- String ARGUMENT_NOT_TEST_PACKAGE = "notPackage";
-
- /**
- * Log the results as if the tests were executed but don't actually run the tests.
- *
- * <p>The equivalent constant in {@code InstrumentationTestRunner} is private.
- */
- String ARGUMENT_LOG_ONLY = "log";
-
- /**
- * Wait for the debugger before starting.
- *
- * <p>There is no equivalent constant in {@code InstrumentationTestRunner} but the string is
- * used
- * within that class.
- */
- String ARGUMENT_DEBUG = "debug";
-
- /**
- * Only count the number of tests to run.
- *
- * <p>There is no equivalent constant in {@code InstrumentationTestRunner} but the string is
- * used
- * within that class.
- */
- String ARGUMENT_COUNT = "count";
-
- /**
- * The per test timeout value.
- */
- String ARGUMENT_TIMEOUT = "timeout_msec";
-
- /**
- * Token representing how long (in seconds) the current test took to execute.
- *
- * <p>The equivalent constant in {@code InstrumentationTestRunner} is private.
- */
- String REPORT_KEY_RUNTIME = "runtime";
-
- /**
- * An identifier for tests run using this class.
- */
- String REPORT_VALUE_ID = "CoreTestRunner";
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
deleted file mode 100644
index 42b6684..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.os.Debug;
-import android.support.test.internal.runner.listener.InstrumentationResultPrinter;
-import android.support.test.internal.runner.listener.InstrumentationRunListener;
-import android.support.test.internal.util.AndroidRunnerParams;
-import android.util.Log;
-import com.android.cts.core.runner.support.ExtendedAndroidRunnerBuilder;
-import com.google.common.base.Splitter;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.junit.runner.Computer;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.Result;
-import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.notification.RunListener;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.RunnerBuilder;
-
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_COUNT;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_CLASS;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_FILE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_PACKAGE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_PACKAGE;
-import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TIMEOUT;
-
-/**
- * A drop-in replacement for AndroidJUnitTestRunner, which understands the same arguments, and has
- * similar functionality, but can filter by expectations and allows a custom runner-builder to be
- * provided.
- */
-public class CoreTestRunner extends Instrumentation {
-
- static final String TAG = "LibcoreTestRunner";
-
- private static final java.lang.String ARGUMENT_ROOT_CLASSES = "core-root-classes";
-
- private static final String ARGUMENT_CORE_LISTENER = "core-listener";
-
- private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
-
- /** The args for the runner. */
- private Bundle args;
-
- /** Only log the number and names of tests, and not run them. */
- private boolean logOnly;
-
- /** The amount of time in millis to wait for a single test to complete. */
- private long testTimeout;
-
- /**
- * The list of tests to run.
- */
- private TestList testList;
-
- /**
- * The list of {@link RunListener} classes to create.
- */
- private List<Class<? extends RunListener>> listenerClasses;
- private Filter expectationFilter;
-
- @Override
- public void onCreate(final Bundle args) {
- super.onCreate(args);
- this.args = args;
-
- boolean debug = "true".equalsIgnoreCase(args.getString(ARGUMENT_DEBUG));
- if (debug) {
- Log.i(TAG, "Waiting for debugger to connect...");
- Debug.waitForDebugger();
- Log.i(TAG, "Debugger connected.");
- }
-
- // Log the message only after getting a value from the args so that the args are
- // unparceled.
- Log.d(TAG, "In OnCreate: " + args);
-
- // Treat logOnly and count as the same. This is not quite true as count should only send
- // the host the number of tests but logOnly should send the name and number. However,
- // this is how this has always behaved and it does not appear to have caused any problems.
- // Changing it seems unnecessary given that count is CTSv1 only and CTSv1 will be removed
- // soon now that CTSv2 is ready.
- boolean testCountOnly = args.getBoolean(ARGUMENT_COUNT);
- this.logOnly = "true".equalsIgnoreCase(args.getString(ARGUMENT_LOG_ONLY)) || testCountOnly;
- this.testTimeout = parseUnsignedLong(args.getString(ARGUMENT_TIMEOUT), ARGUMENT_TIMEOUT);
-
- expectationFilter = new ExpectationBasedFilter(args);
-
- // The test can be run specifying a list of tests to run, or as cts-tradefed does it,
- // by passing a fileName with a test to run on each line.
- Set<String> testNameSet = new HashSet<>();
- String arg;
- if ((arg = args.getString(ARGUMENT_TEST_FILE)) != null) {
- // The tests are specified in a file.
- try {
- testNameSet.addAll(readTestsFromFile(arg));
- } catch (IOException err) {
- finish(Activity.RESULT_CANCELED, new Bundle());
- return;
- }
- } else if ((arg = args.getString(ARGUMENT_TEST_CLASS)) != null) {
- // The tests are specified in a String passed in the bundle.
- String[] tests = arg.split(",");
- testNameSet.addAll(Arrays.asList(tests));
- }
-
- // Tests may be excluded from the run by passing a list of tests not to run,
- // or by passing a fileName with a test not to run on each line.
- Set<String> notTestNameSet = new HashSet<>();
- if ((arg = args.getString(ARGUMENT_NOT_TEST_FILE)) != null) {
- // The tests are specified in a file.
- try {
- notTestNameSet.addAll(readTestsFromFile(arg));
- } catch (IOException err) {
- finish(Activity.RESULT_CANCELED, new Bundle());
- return;
- }
- } else if ((arg = args.getString(ARGUMENT_NOT_TEST_CLASS)) != null) {
- // The classes are specified in a String passed in the bundle
- String[] tests = arg.split(",");
- notTestNameSet.addAll(Arrays.asList(tests));
- }
-
- Set<String> packageNameSet = new HashSet<>();
- if ((arg = args.getString(ARGUMENT_TEST_PACKAGE)) != null) {
- // The packages are specified in a String passed in the bundle
- String[] packages = arg.split(",");
- packageNameSet.addAll(Arrays.asList(packages));
- }
-
- Set<String> notPackageNameSet = new HashSet<>();
- if ((arg = args.getString(ARGUMENT_NOT_TEST_PACKAGE)) != null) {
- // The packages are specified in a String passed in the bundle
- String[] packages = arg.split(",");
- notPackageNameSet.addAll(Arrays.asList(packages));
- }
-
- List<String> roots = getRootClassNames(args);
- if (roots == null) {
- // Find all test classes
- Collection<Class<?>> classes = TestClassFinder.getClasses(
- Collections.singletonList(getContext().getPackageCodePath()),
- getClass().getClassLoader());
- testList = new TestList(classes);
- } else {
- testList = TestList.rootList(roots);
- }
-
- testList.addIncludeTestPackages(packageNameSet);
- testList.addExcludeTestPackages(notPackageNameSet);
- testList.addIncludeTests(testNameSet);
- testList.addExcludeTests(notTestNameSet);
-
- listenerClasses = new ArrayList<>();
- String listenerArg = args.getString(ARGUMENT_CORE_LISTENER);
- if (listenerArg != null) {
- List<String> listenerClassNames = CLASS_LIST_SPLITTER.splitToList(listenerArg);
- for (String listenerClassName : listenerClassNames) {
- try {
- Class<? extends RunListener> listenerClass = Class.forName(listenerClassName)
- .asSubclass(RunListener.class);
- listenerClasses.add(listenerClass);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Could not load listener class: " + listenerClassName, e);
- }
- }
- }
-
- start();
- }
-
- private List<String> getRootClassNames(Bundle args) {
- String rootClasses = args.getString(ARGUMENT_ROOT_CLASSES);
- List<String> roots;
- if (rootClasses == null) {
- roots = null;
- } else {
- roots = CLASS_LIST_SPLITTER.splitToList(rootClasses);
- }
- return roots;
- }
-
- @Override
- public void onStart() {
- if (logOnly) {
- Log.d(TAG, "Counting/logging tests only");
- } else {
- Log.d(TAG, "Running tests");
- }
-
- AndroidRunnerParams runnerParams = new AndroidRunnerParams(this, args,
- false, testTimeout, false /*ignoreSuiteMethods*/);
-
- Runner runner;
- try {
- RunnerBuilder runnerBuilder = new ExtendedAndroidRunnerBuilder(runnerParams);
- Class[] classes = testList.getClassesToRun();
- for (Class cls : classes) {
- Log.d(TAG, "Found class to run: " + cls.getName());
- }
- runner = new Computer().getSuite(runnerBuilder, classes);
-
- if (runner instanceof Filterable) {
- Log.d(TAG, "Applying filters");
- Filterable filterable = (Filterable) runner;
-
- // Filter out all the tests that are expected to fail.
- try {
- filterable.filter(expectationFilter);
- } catch (NoTestsRemainException e) {
- // Sometimes filtering will remove all tests but we do not care about that.
- }
- Log.d(TAG, "Applied filters");
- }
-
- // If the tests are only supposed to be logged and not actually run then replace the
- // runner with a runner that will fire notifications for all the tests that would have
- // been run. This is needed because CTSv2 does a log only run through a CTS module in
- // order to generate a list of tests that will be run so that it can monitor them.
- // Encapsulating that in a Runner implementation makes it easier to leverage the
- // existing code for running tests.
- if (logOnly) {
- runner = new DescriptionHierarchyNotifier(runner.getDescription());
- }
-
- } catch (InitializationError e) {
- throw new RuntimeException("Could not create a suite", e);
- }
-
- InstrumentationResultPrinter instrumentationResultPrinter =
- new InstrumentationResultPrinter();
- instrumentationResultPrinter.setInstrumentation(this);
-
- JUnitCore core = new JUnitCore();
- core.addListener(instrumentationResultPrinter);
-
- // If not logging the list of tests then add any additional configured listeners. These
- // must be added before firing any events.
- if (!logOnly) {
- // Add additional configured listeners.
- for (Class<? extends RunListener> listenerClass : listenerClasses) {
- try {
- RunListener runListener = listenerClass.newInstance();
- if (runListener instanceof InstrumentationRunListener) {
- ((InstrumentationRunListener) runListener).setInstrumentation(this);
- }
- core.addListener(runListener);
- } catch (InstantiationException | IllegalAccessException e) {
- Log.e(TAG,
- "Could not create instance of listener: " + listenerClass, e);
- }
- }
- }
-
- Log.d(TAG, "Finished preparations, running/listing tests");
-
- Bundle results = new Bundle();
- Result junitResults = new Result();
- try {
- junitResults = core.run(Request.runner(runner));
- } catch (RuntimeException e) {
- final String msg = "Fatal exception when running tests";
- Log.e(TAG, msg, e);
- // report the exception to instrumentation out
- results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
- msg + "\n" + Log.getStackTraceString(e));
- } finally {
- ByteArrayOutputStream summaryStream = new ByteArrayOutputStream();
- // create the stream used to output summary data to the user
- PrintStream summaryWriter = new PrintStream(summaryStream);
- instrumentationResultPrinter.instrumentationRunFinished(summaryWriter,
- results, junitResults);
- summaryWriter.close();
- results.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
- String.format("\n%s", summaryStream.toString()));
- }
-
-
- Log.d(TAG, "Finished");
- finish(Activity.RESULT_OK, results);
- }
-
- /**
- * Read tests from a specified file.
- *
- * @return class names of tests. If there was an error reading the file, null is returned.
- */
- private static List<String> readTestsFromFile(String fileName) throws IOException {
- try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
- List<String> tests = new ArrayList<>();
- String line;
- while ((line = br.readLine()) != null) {
- tests.add(line);
- }
- return tests;
- } catch (IOException err) {
- Log.e(TAG, "There was an error reading the test class list: " + err.getMessage());
- throw err;
- }
- }
-
- /**
- * Parse long from given value - except either Long or String.
- *
- * @return the value, -1 if not found
- * @throws NumberFormatException if value is negative or not a number
- */
- private static long parseUnsignedLong(Object value, String name) {
- if (value != null) {
- long longValue = Long.parseLong(value.toString());
- if (longValue < 0) {
- throw new NumberFormatException(name + " can not be negative");
- }
- return longValue;
- }
- return -1;
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/DescriptionHierarchyNotifier.java b/tests/core/runner/src/com/android/cts/core/runner/DescriptionHierarchyNotifier.java
deleted file mode 100644
index 5c94fd7..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/DescriptionHierarchyNotifier.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner;
-
-import java.util.List;
-import org.junit.runner.Description;
-import org.junit.runner.Runner;
-import org.junit.runner.notification.RunNotifier;
-
-/**
- * A {@link Runner} that does not actually run any tests but simply fires events for all leaf
- * {@link Description} instances in the supplied {@link Description} hierarchy.
- */
-class DescriptionHierarchyNotifier extends Runner {
-
- private final Description description;
-
- DescriptionHierarchyNotifier(Description description) {
- this.description = description;
- }
-
- @Override
- public Description getDescription() {
- return description;
- }
-
- @Override
- public void run(RunNotifier notifier) {
- generateListOfTests(notifier, description);
- }
-
- /**
- * Generates a list of tests to run by recursing over the {@link Description} hierarchy and
- * firing events to simulate the tests being run successfully.
- * @param runNotifier the notifier to which the events are sent.
- * @param description the description to traverse.
- */
- private void generateListOfTests(RunNotifier runNotifier, Description description) {
- List<Description> children = description.getChildren();
- if (children.isEmpty()) {
- runNotifier.fireTestStarted(description);
- runNotifier.fireTestFinished(description);
- } else {
- for (Description child : children) {
- generateListOfTests(runNotifier, child);
- }
- }
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java b/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
index 90034ec..f409dbd 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
@@ -105,7 +105,7 @@
if (expectationStore != null) {
Expectation expectation = expectationStore.get(testName);
if (expectation.getResult() != Result.SUCCESS) {
- Log.d(CoreTestRunner.TAG, "Excluding test " + testDescription
+ Log.d(TAG, "Excluding test " + testDescription
+ " as it matches expectation: " + expectation);
return false;
}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java b/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
deleted file mode 100644
index 6ad95f6..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner;
-
-import android.support.test.internal.runner.ClassPathScanner;
-import android.support.test.internal.runner.ClassPathScanner.ClassNameFilter;
-import android.util.Log;
-
-import com.android.cts.core.internal.runner.TestLoader;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Set;
-
-import dalvik.system.DexFile;
-
-/**
- * Find tests in the current APK.
- */
-public class TestClassFinder {
-
- private static final String TAG = "TestClassFinder";
- private static final boolean DEBUG = false;
-
- // Excluded test packages
- private static final String[] DEFAULT_EXCLUDED_PACKAGES = {
- "junit",
- "org.junit",
- "org.hamcrest",
- "org.mockito",// exclude Mockito for performance and to prevent JVM related errors
- "android.support.test.internal.runner.junit3",// always skip AndroidTestSuite
- };
-
- static Collection<Class<?>> getClasses(List<String> apks, ClassLoader loader) {
- if (DEBUG) {
- Log.d(TAG, "getClasses: =======================================");
-
- for (String apkPath : apks) {
- Log.d(TAG, "getClasses: -------------------------------");
- Log.d(TAG, "getClasses: APK " + apkPath);
-
- DexFile dexFile = null;
- try {
- dexFile = new DexFile(apkPath);
- Enumeration<String> apkClassNames = dexFile.entries();
- while (apkClassNames.hasMoreElements()) {
- String apkClassName = apkClassNames.nextElement();
- Log.d(TAG, "getClasses: DexClass element " + apkClassName);
- }
- } catch (IOException e) {
- throw new AssertionError(e);
- } finally {
- if (dexFile != null) {
- try {
- dexFile.close();
- } catch (IOException e) {
- throw new AssertionError(e);
- }
- }
- }
- Log.d(TAG, "getClasses: -------------------------------");
- }
- } // if DEBUG
-
- List<Class<?>> classes = new ArrayList<>();
- ClassPathScanner scanner = new ClassPathScanner(apks);
-
- ClassPathScanner.ChainedClassNameFilter filter =
- new ClassPathScanner.ChainedClassNameFilter();
- // exclude inner classes
- filter.add(new ClassPathScanner.ExternalClassNameFilter());
-
- // exclude default classes
- for (String defaultExcludedPackage : DEFAULT_EXCLUDED_PACKAGES) {
- filter.add(new ExcludePackageNameFilter(defaultExcludedPackage));
- }
-
- // exclude any classes that aren't a "test class" (see #loadIfTest)
- TestLoader testLoader = new TestLoader();
- testLoader.setClassLoader(loader);
-
- try {
- Set<String> classNames = scanner.getClassPathEntries(filter);
- for (String className : classNames) {
- // Important: This further acts as an additional filter;
- // classes that aren't a "test class" are never loaded.
- Class<?> cls = testLoader.loadIfTest(className);
- if (cls != null) {
- classes.add(cls);
-
- if (DEBUG) {
- Log.d(TAG, "getClasses: Loaded " + className);
- }
- } else if (DEBUG) {
- Log.d(TAG, "getClasses: Failed to load class " + className);
- }
- }
- return classes;
- } catch (IOException e) {
- Log.e(CoreTestRunner.TAG, "Failed to scan classes", e);
- }
-
-
- if (DEBUG) {
- Log.d(TAG, "getClasses: =======================================");
- }
-
- return testLoader.getLoadedClasses();
- }
-
- /**
- * A {@link ClassNameFilter} that only rejects a given package names within the given namespace.
- */
- public static class ExcludePackageNameFilter implements ClassNameFilter {
-
- private final String mPkgName;
-
- ExcludePackageNameFilter(String pkgName) {
- if (!pkgName.endsWith(".")) {
- mPkgName = String.format("%s.", pkgName);
- } else {
- mPkgName = pkgName;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean accept(String pathName) {
- return !pathName.startsWith(mPkgName);
- }
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/TestList.java b/tests/core/runner/src/com/android/cts/core/runner/TestList.java
deleted file mode 100644
index 9d97501..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/TestList.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner;
-
-import android.util.Log;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.Nullable;
-
-/**
- * A list of the tests to run.
- */
-class TestList {
-
- /** The set of test pacakges to run */
- private final Set<String> mIncludedPackages = new HashSet<>();
-
- /** The set of test packages not to run */
- private final Set<String> mExcludedPackages = new HashSet<>();
-
- /** The set of tests (classes or methods) to run */
- private final Set<String> mIncludedTests = new HashSet<>();
-
- /** The set of tests (classes or methods) not to run */
- private final Set<String> mExcludedTests = new HashSet<>();
-
- /** The list of all test classes to run (without filtering applied)*/
- private final Collection<Class<?>> classesToRun;
-
- public static TestList rootList(List<String> rootList) {
-
- // Run from the root test class.
- Set<String> classNamesToRun = new LinkedHashSet<>(rootList);
- Log.d(CoreTestRunner.TAG, "Running all tests rooted at " + classNamesToRun);
-
- List<Class<?>> classesToRun1 = getClasses(classNamesToRun);
-
- return new TestList(classesToRun1);
- }
-
- private static List<Class<?>> getClasses(Set<String> classNames) {
- // Populate the list of classes to run.
- List<Class<?>> classesToRun = new ArrayList<>();
- for (String className : classNames) {
- try {
- classesToRun.add(Class.forName(className));
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException("Could not load class '" + className, e);
- }
- }
- return classesToRun;
- }
-
- /**
- * @param classes The list of classes to run.
- */
- public TestList(Collection<Class<?>> classes) {
- this.classesToRun = classes;
- }
-
- public void addIncludeTestPackages(Set<String> packageNameSet) {
- mIncludedPackages.addAll(packageNameSet);
- }
-
- public void addExcludeTestPackages(Set<String> packageNameSet) {
- mExcludedPackages.addAll(packageNameSet);
- }
-
- public void addIncludeTests(Set<String> testNameSet) {
- mIncludedTests.addAll(testNameSet);
- }
-
- public void addExcludeTests(Set<String> testNameSet) {
- mExcludedTests.addAll(testNameSet);
- }
-
- /**
- * Return all the classes to run.
- */
- public Class[] getClassesToRun() {
- return classesToRun.toArray(new Class[classesToRun.size()]);
- }
-
- /**
- * Return true if the test with the specified name should be run, false otherwise.
- */
- public boolean shouldRunTest(String testName) {
-
- int index = testName.indexOf('#');
- String className;
- if (index == -1) {
- className = testName;
- } else {
- className = testName.substring(0, index);
- }
- try {
- Class<?> testClass = Class.forName(className);
- Package testPackage = testClass.getPackage();
- String testPackageName = "";
- if (testPackage != null) {
- testPackageName = testPackage.getName();
- }
-
- boolean include =
- (mIncludedPackages.isEmpty() || mIncludedPackages.contains(testPackageName)) &&
- (mIncludedTests.isEmpty() || mIncludedTests.contains(className) ||
- mIncludedTests.contains(testName));
-
- boolean exclude =
- mExcludedPackages.contains(testPackageName) ||
- mExcludedTests.contains(className) ||
- mExcludedTests.contains(testName);
-
- return include && !exclude;
- } catch (ClassNotFoundException e) {
- Log.w("Could not load class '" + className, e);
- return false;
- }
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java
deleted file mode 100644
index 91df2b9..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/AndroidRunnerBuilder.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner.support;
-
-import android.support.test.internal.runner.junit3.AndroidJUnit3Builder;
-import android.support.test.internal.runner.junit3.AndroidSuiteBuilder;
-import android.support.test.internal.runner.junit4.AndroidAnnotatedBuilder;
-import android.support.test.internal.runner.junit4.AndroidJUnit4Builder;
-import android.support.test.internal.util.AndroidRunnerParams;
-
-import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
-import org.junit.internal.builders.AnnotatedBuilder;
-import org.junit.internal.builders.IgnoredBuilder;
-import org.junit.internal.builders.JUnit3Builder;
-import org.junit.internal.builders.JUnit4Builder;
-import org.junit.runners.model.RunnerBuilder;
-
-/**
- * A {@link RunnerBuilder} that can handle all types of tests.
- */
-// A copy of package private class android.support.test.internal.runner.AndroidRunnerBuilder.
-// Copied here so that it can be extended.
-class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
-
- private final AndroidJUnit3Builder mAndroidJUnit3Builder;
- private final AndroidJUnit4Builder mAndroidJUnit4Builder;
- private final AndroidSuiteBuilder mAndroidSuiteBuilder;
- private final AndroidAnnotatedBuilder mAndroidAnnotatedBuilder;
- // TODO: customize for Android ?
- private final IgnoredBuilder mIgnoredBuilder;
-
- /**
- * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
- */
- // Added canUseSuiteMethod parameter.
- AndroidRunnerBuilder(AndroidRunnerParams runnerParams, boolean canUseSuiteMethod) {
- super(canUseSuiteMethod);
- mAndroidJUnit3Builder = new AndroidJUnit3Builder(runnerParams);
- mAndroidJUnit4Builder = new AndroidJUnit4Builder(runnerParams);
- mAndroidSuiteBuilder = new AndroidSuiteBuilder(runnerParams);
- mAndroidAnnotatedBuilder = new AndroidAnnotatedBuilder(this, runnerParams);
- mIgnoredBuilder = new IgnoredBuilder();
- }
-
- @Override
- protected JUnit4Builder junit4Builder() {
- return mAndroidJUnit4Builder;
- }
-
- @Override
- protected JUnit3Builder junit3Builder() {
- return mAndroidJUnit3Builder;
- }
-
- @Override
- protected AnnotatedBuilder annotatedBuilder() {
- return mAndroidAnnotatedBuilder;
- }
-
- @Override
- protected IgnoredBuilder ignoredBuilder() {
- return mIgnoredBuilder;
- }
-
- @Override
- protected RunnerBuilder suiteMethodBuilder() {
- return mAndroidSuiteBuilder;
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
deleted file mode 100644
index a2f95e2..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner.support;
-
-import android.support.test.internal.util.AndroidRunnerParams;
-import android.util.Log;
-import org.junit.runners.model.RunnerBuilder;
-import org.junit.runner.Runner;
-
-/**
- * Extends {@link AndroidRunnerBuilder} in order to provide alternate {@link RunnerBuilder}
- * implementations.
- */
-public class ExtendedAndroidRunnerBuilder extends AndroidRunnerBuilder {
-
- private static final boolean DEBUG = false;
-
- private final TestNgRunnerBuilder mTestNgBuilder;
-
- /**
- * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
- */
- public ExtendedAndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
- super(runnerParams, false /* CTSv1 filtered out Test suite() classes. */);
- mTestNgBuilder = new TestNgRunnerBuilder();
- }
-
- @Override
- public Runner runnerForClass(Class<?> testClass) throws Throwable {
- if (DEBUG) {
- Log.d("ExAndRunBuild", "runnerForClass: Searching runner for class " + testClass.getName());
- }
-
- // Give TestNG tests a chance to participate in the Runner search first.
- // (Note that the TestNG runner handles log-only runs by itself)
- Runner runner = mTestNgBuilder.runnerForClass(testClass);
- if (runner == null) {
- // Use the normal Runner search mechanism (for Junit tests).
- runner = super.runnerForClass(testClass);
- }
-
- logFoundRunner(runner);
- return runner;
- }
-
- private static void logFoundRunner(Runner runner) {
- if (DEBUG) {
- Log.d("ExAndRunBuild", "runnerForClass: Found runner of type " +
- ((runner == null) ? "<null>" : runner.getClass().getName()));
- }
- }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java b/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java
deleted file mode 100644
index 3ccec3c..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/package-info.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Contains all the changes needed to the {@code android.support.test.internal.runner} classes.
- *
- * <p>As its name suggests {@code android.support.test.internal.runner} are internal classes that
- * are not designed to be extended from outside those packages. This package encapsulates all the
- * workarounds needed to overcome that limitation, from duplicating classes to using reflection.
- * The intention is that these changes are temporary and they (or a better equivalent) will be
- * quickly integrated into the internal classes.
- */
-package com.android.cts.core.runner.support;
diff --git a/tests/dram/AndroidTest.xml b/tests/dram/AndroidTest.xml
index 636c8c1..5c31a36 100644
--- a/tests/dram/AndroidTest.xml
+++ b/tests/dram/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Dram test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
deleted file mode 100644
index 08c2a5e..0000000
--- a/tests/expectations/knownfailures.txt
+++ /dev/null
@@ -1,282 +0,0 @@
-[
-{
- description: "Disable ListeningPortsTest",
- names: [
- "android.security.cts.ListeningPortsTest"
- ],
- bug: 31803630
-},
-{
- description: "some AlarmClockTests are not robust across different device types",
- names: [
- "android.alarmclock.cts.DismissAlarmTest#testAll",
- "android.alarmclock.cts.SetAlarmTest#testAll",
- "android.alarmclock.cts.SnoozeAlarmTest#testAll"
- ],
- bug: 23776083
-},
-{
- description: "the UsageStats is not yet stable enough",
- names: [
- "android.app.usage.cts.UsageStatsTest"
- ],
- bug: 17536113
-},
-{
- description: "the ConnectivityConstraintTest are not yet stable",
- names: [
- "android.jobscheduler.cts.ConnectivityConstraintTest"
- ],
- bug: 18117279
-},
-{
- description: "tests a fragile by nature as they rely on hardcoded behavior",
- names: [
- "android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText",
- "android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverTextExtend"
- ],
- bug: 17595050
-},
-{
- description: "test fails on some devices",
- names: [
- "android.dumpsys.cts.DumpsysHostTest#testBatterystatsOutput",
- "android.dumpsys.cts.DumpsysHostTest#testGfxinfoFramestats"
- ],
- bug: 23776893
-},
-{
- description: "the SSLCertificateSocketFactoryTest often fails because of lack of live internet or short timeout, it should be refactored to do a local server testing",
- names: [
- "android.net.cts.SSLCertificateSocketFactoryTest#testCreateSocket",
- "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_bind",
- "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_simple",
- "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_wrapping"
- ],
- bug: 18682315
-},
-{
- description: "the test result are too much dependent on live-internet connection, which for some devices might not exist",
- names: [
- "android.net.wifi.cts.NsdManagerTest#testAndroidTestCaseSetupProperly"
- ],
- bug: 18680089
-},
-{
- description: "AudioPolicyBinder tests are not yet robust enough",
- names: [
- "android.security.cts.AudioPolicyBinderTest"
- ],
- bug: 18461670
-},
-{
- description: "test not robust",
- names: [
- "android.telecom.cts.ExtendedInCallServiceTest#testAddNewOutgoingCallAndThenDisconnect",
- "android.telecom.cts.RemoteConferenceTest#testRemoteConferenceCallbacks_ConferenceableConnections"
- ],
- bug: 23604254
-},
-{
- description: "tests too flaky",
- names: [
- "android.transition.cts.ChangeScrollTest#testChangeScroll"
- ],
- bug: 23779020
-},
-{
- description: "Not all jdwp features are currently supported. These tests will fail",
- names: [
- "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch001",
- "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch002",
- "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch003",
- "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch004",
- "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowLaunchDebugger001#testDebugger002",
- "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowLaunchDebugger002#testDebugger",
- "org.apache.harmony.jpda.tests.jdwp.Events.ClassUnloadTest#testClassUnloadEvent",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorContendedEnterTest#testMonitorContendedEnterForClassMatch",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorContendedEnteredTest#testMonitorContendedEnteredForClassMatch",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassExclude",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassMatchExact",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassMatchFirst",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassMatchSecond",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitTest#testMonitorWaitForClassOnly",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassExclude",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassMatchExact",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassMatchFirst",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassMatchSecond",
- "org.apache.harmony.jpda.tests.jdwp.Events.MonitorWaitedTest#testMonitorWaitedForClassOnly",
- "org.apache.harmony.jpda.tests.jdwp.ReferenceType.ClassFileVersionTest#testClassFileVersion001",
- "org.apache.harmony.jpda.tests.jdwp.ReferenceType.NestedTypesTest#testNestedTypes001",
- "org.apache.harmony.jpda.tests.jdwp.ThreadReference.StopTest#testStop001",
- "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.HoldEventsTest#testHoldEvents001",
- "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.ReleaseEventsTest#testReleaseEvents001"
- ],
- bug: 16720689
-},
-{
- description: "test can only run properly on a user build device when the bug is resolved",
- names: [
- "android.appwidget.cts.AppWidgetTest#testAppWidgetProviderCallbacks",
- "android.appwidget.cts.AppWidgetTest#testBindAppWidget",
- "android.appwidget.cts.AppWidgetTest#testCollectionWidgets",
- "android.appwidget.cts.AppWidgetTest#testDeleteHost",
- "android.appwidget.cts.AppWidgetTest#testDeleteHosts",
- "android.appwidget.cts.AppWidgetTest#testGetAppWidgetIds",
- "android.appwidget.cts.AppWidgetTest#testGetAppWidgetInfo",
- "android.appwidget.cts.AppWidgetTest#testGetAppWidgetOptions",
- "android.appwidget.cts.AppWidgetTest#testPartiallyUpdateAppWidgetViaWidgetId",
- "android.appwidget.cts.AppWidgetTest#testPartiallyUpdateAppWidgetViaWidgetIds",
- "android.appwidget.cts.AppWidgetTest#testTwoAppWidgetProviderCallbacks",
- "android.appwidget.cts.AppWidgetTest#testUpdateAppWidgetViaComponentName",
- "android.appwidget.cts.AppWidgetTest#testUpdateAppWidgetViaWidgetId",
- "android.appwidget.cts.AppWidgetTest#testUpdateAppWidgetViaWidgetIds"
- ],
- bug: 17993121
-},
-{
- description: "permissions for the API previously used in the test has changed, making it impossible to pass",
- names: [
- "android.openglperf.cts.GlAppSwitchTest#testGlActivitySwitchingFast",
- "android.openglperf.cts.GlAppSwitchTest#testGlActivitySwitchingSlow"
- ],
- bug: 17394321
-},
-{
- description: "unexpected failures",
- names: [
- "android.openglperf.cts.GlVboPerfTest#testVboWithVaryingIndexBufferNumbers"
- ],
- bug: 18091590
-},
-{
- description: "Test is not yet properly implemented",
- names: [
- "android.voicesettings.cts.ZenModeTest#testAll"
- ],
- bug: 23238984
-},
-{
- description: "This test failed on devices that use effect off loading. In addition it uses hidden apis",
- names: [
- "android.media.cts.AudioEffectTest#test1_1ConstructorFromUuid"
- ],
- bug: 17605875
-},
-{
- description: "This test failed on hw decoder that doesn't output frame with the configured format.",
- names: [
- "android.media.cts.ImageReaderDecoderTest#testHwAVCDecode360pForFlexibleYuv"
- ],
- bug: 17144778
-},
-{
- description: "android.keystore tests will replace these tests",
- names: [
- "com.android.org.conscrypt.MacTest#test_getInstance_OpenSSL_ENGINE",
- "com.android.org.conscrypt.NativeCryptoTest#test_ENGINE_by_id_TestEngine",
- "com.android.org.conscrypt.SignatureTest#test_getInstance_OpenSSL_ENGINE"
- ],
- bug: 18030049
-},
-{
- description: "The new prepare performance test is not yet passing on all devices",
- names: [
- "android.hardware.camera2.cts.SurfaceViewPreviewTest#testPreparePerformance"
- ],
- bug: 17989532
-},
-{
- description: "The timing measurements for preview callbacks are not reliable",
- names: [
- "android.hardware.cts.CameraTest#testPreviewFpsRange"
- ],
- bug: 23008511
-},
-{
- description: "Light status bar CTS coming in late",
- names: [
- "android.systemui.cts.LightStatusBarTests#testLightStatusBarIcons"
- ],
- bug: 23427621
-},
-{
- description: "tests are not yet ready",
- names: [
- "com.android.cts.app.os.OsHostTests#testNonExportedActivities"
- ],
- bug: 23779168
-},
-{
- description: "ConnectivityConstraintTest job scheduler not working.",
- names: [
- "android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withWifi",
- "android.jobscheduler.cts.ConnectivityConstraintTest#testUnmeteredConstraintExecutes_withWifi",
- "android.jobscheduler.cts.ConnectivityConstraintTest#testConnectivityConstraintExecutes_withMobile"
- ],
- bug: 21262226
-},
-{
- description: "ConnectivityConstraintTest times out.",
- names: [
- "android.jobscheduler.cts.TimingConstraintsTest#testJobParameters_unexpiredDeadline"
- ],
- bug: 23144425
-},
-{
- description: "Video encoding tests are timing out.",
- names: [
- "android.media.cts.VideoEncoderTest#testGoogH264FlexArbitraryW",
- "android.media.cts.VideoEncoderTest#testGoogH264SurfArbitraryW"
- ],
- bug: 23827982
-},
-{
- description: "protected broadcast not working",
- names: [
- "android.permission2.cts.ProtectedBroadcastsTest#testSendProtectedBroadcasts"
- ],
- bug: 23192492
-},
-{
- description: "restricted network is not working",
- names: [
- "android.net.cts.ConnectivityManagerTest#testRestrictedNetworks"
- ],
- bug: 25651805
-},
-{
- description: "unit testing for MediaPreparer lives within mediastress module",
- names: [
- "android.mediastress.cts.preconditions.MediaPreparerTest"
- ],
- bug: 25850508
-},
-{
- description: "Tests for the signature tests should not be in CTS",
- names: [
- "android.signature.cts.tests"
- ],
- bug: 26150806
-},
-{
- description: "android.security.cts is using a Non-NDK library, libmedia_jni.so",
- names: [
- "android.security.cts.MediaCryptoTest#testMediaCryptoClearKey",
- "android.security.cts.MediaCryptoTest#testMediaCryptoWidevine"
- ],
- bug: 27218502
-},
-{
- description: "Still investigating this, root cause unknown yet",
- bug: 27578806,
- names: ["com.android.cts.cpptools.RunAsHostTest#testRunAs"]
-},
-{
- description: "Wired headset tests are no longer a requirement per CDD",
- names: [
- "android.telecom.cts.WiredHeadsetTest"
- ],
- bug: 26149528
-}
-]
diff --git a/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
index 5f3923b..fed22df 100644
--- a/tests/filesystem/AndroidTest.xml
+++ b/tests/filesystem/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS File System test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/fragment/Android.mk b/tests/fragment/Android.mk
index 1785097..0a54354 100644
--- a/tests/fragment/Android.mk
+++ b/tests/fragment/Android.mk
@@ -33,8 +33,8 @@
mockito-target-minus-junit4 \
android-common \
compatibility-device-util \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
#LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/fragment/AndroidTest.xml b/tests/fragment/AndroidTest.xml
index 4e563d9..f352ef2 100644
--- a/tests/fragment/AndroidTest.xml
+++ b/tests/fragment/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for app.usage Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/services/Android.mk b/tests/framework/Android.mk
similarity index 100%
rename from hostsidetests/services/Android.mk
rename to tests/framework/Android.mk
diff --git a/hostsidetests/services/Android.mk b/tests/framework/base/Android.mk
similarity index 100%
copy from hostsidetests/services/Android.mk
copy to tests/framework/base/Android.mk
diff --git a/tests/framework/base/activitymanager/Android.mk b/tests/framework/base/activitymanager/Android.mk
new file mode 100644
index 0000000..ee5ff8c
--- /dev/null
+++ b/tests/framework/base/activitymanager/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_PACKAGE_NAME := CtsActivityManagerDeviceTestCases
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ cts-amwm-util \
+ cts-display-service-app-util
+
+LOCAL_CTS_TEST_PACKAGE := android.server
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
new file mode 100644
index 0000000..15c07be
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.cts.am">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+ <application android:label="CtsActivityManagerDeviceTestCases">
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="android.server.am.AspectRatioTests$MaxAspectRatioActivity"
+ android:label="MaxAspectRatioActivity"
+ android:maxAspectRatio="1.0"
+ android:resizeableActivity="false" />
+
+ <activity
+ android:name="android.server.am.AspectRatioTests$MetaDataMaxAspectRatioActivity"
+ android:label="MetaDataMaxAspectRatioActivity"
+ android:resizeableActivity="false">
+ <meta-data
+ android:name="android.max_aspect"
+ android:value="1.0" />
+ </activity>
+
+ <activity
+ android:name="android.server.am.AspectRatioTests$MaxAspectRatioResizeableActivity"
+ android:label="MaxAspectRatioResizeableActivity"
+ android:maxAspectRatio="1.0"
+ android:resizeableActivity="true" />
+
+ <activity
+ android:name="android.server.am.AspectRatioTests$MaxAspectRatioUnsetActivity"
+ android:label="MaxAspectRatioUnsetActivity"
+ android:resizeableActivity="false" />
+
+ <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$FirstActivity" />
+
+ <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SecondActivity"/>
+
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests of ActivityManager"
+ android:targetPackage="android.server.cts.am" />
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/AndroidTest.xml b/tests/framework/base/activitymanager/AndroidTest.xml
new file mode 100644
index 0000000..bcdaf5c
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS ActivityManager test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsActivityManagerDeviceTestCases.apk" />
+ <option name="test-file-name" value="CtsDeviceServicesTestApp.apk" />
+ <option name="test-file-name" value="CtsDeviceServicesTestSecondApp.apk" />
+ <option name="test-file-name" value="CtsDeviceServicesTestThirdApp.apk" />
+ <option name="test-file-name" value="CtsDeviceDebuggableApp.apk" />
+ <option name="test-file-name" value="CtsDeviceDisplaySizeApp.apk" />
+ <option name="test-file-name" value="CtsDevicePrereleaseSdkApp.apk" />
+ <option name="test-file-name" value="CtsDisplayServiceApp.apk" />
+ <option name="test-file-name" value="CtsDeviceTranslucentTestApp.apk" />
+ <option name="test-file-name" value="CtsDeviceTranslucentTestApp26.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.server.cts.am"/>
+ <option name="runtime-hint" value="1h"/>
+ </test>
+</configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/Android.mk b/tests/framework/base/activitymanager/app/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/Android.mk
rename to tests/framework/base/activitymanager/app/Android.mk
diff --git a/tests/framework/base/activitymanager/app/AndroidManifest.xml b/tests/framework/base/activitymanager/app/AndroidManifest.xml
new file mode 100755
index 0000000..2bed053
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/AndroidManifest.xml
@@ -0,0 +1,410 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="android.server.am">
+
+ <!-- virtual display test permissions -->
+ <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+
+ <application>
+ <activity android:name=".TestActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:exported="true"
+ />
+ <activity android:name=".TestActivityWithSameAffinity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:exported="true"
+ android:taskAffinity="nobody.but.PipActivitySameAffinity"
+ />
+ <activity android:name=".TranslucentTestActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:theme="@style/Theme.Transparent" />
+ <activity android:name=".VrTestActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ />
+ <activity android:name=".ResumeWhilePausingActivity"
+ android:allowEmbedded="true"
+ android:resumeWhilePausing="true"
+ android:taskAffinity=""
+ android:exported="true"
+ />
+ <activity android:name=".ResizeableActivity"
+ android:resizeableActivity="true"
+ android:allowEmbedded="true"
+ android:exported="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"
+ />
+ <activity android:name=".NonResizeableActivity"
+ android:resizeableActivity="false"
+ android:exported="true"
+ />
+ <activity android:name=".DockedActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ android:taskAffinity="nobody.but.DockedActivity"
+ />
+ <activity android:name=".TranslucentActivity"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
+ android:resizeableActivity="true"
+ android:taskAffinity="nobody.but.TranslucentActivity"
+ android:exported="true"
+ />
+ <activity android:name=".DialogWhenLargeActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"
+ />
+ <activity android:name=".NoRelaunchActivity"
+ android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
+ android:exported="true"
+ android:taskAffinity="nobody.but.NoRelaunchActivity"
+ />
+ <activity android:name=".SlowCreateActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ />
+ <activity android:name=".LaunchingActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ android:taskAffinity="nobody.but.LaunchingActivity"
+ />
+ <activity android:name=".AltLaunchingActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ android:taskAffinity="nobody.but.LaunchingActivity"
+ />
+ <activity android:name=".PipActivity"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ android:taskAffinity="nobody.but.PipActivity"
+ />
+ <activity android:name=".PipActivity2"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ android:taskAffinity="nobody.but.PipActivity2"
+ />
+ <activity android:name=".PipOnStopActivity"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ android:taskAffinity="nobody.but.PipOnStopActivity"
+ />
+ <activity android:name=".PipActivityWithSameAffinity"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ android:taskAffinity="nobody.but.PipActivitySameAffinity"
+ />
+ <activity android:name=".AlwaysFocusablePipActivity"
+ android:theme="@style/Theme.Transparent"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ androidprv:alwaysFocusable="true"
+ android:exported="true"
+ android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
+ />
+ <activity android:name=".LaunchIntoPinnedStackPipActivity"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ androidprv:alwaysFocusable="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ />
+ <activity android:name=".LaunchPipOnPipActivity"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ />
+ <activity android:name=".LaunchEnterPipActivity"
+ android:resizeableActivity="false"
+ android:supportsPictureInPicture="true"
+ androidprv:alwaysFocusable="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true"
+ />
+ <activity android:name=".FreeformActivity"
+ android:resizeableActivity="true"
+ android:taskAffinity="nobody.but.FreeformActivity"
+ android:exported="true"
+ />
+ <activity android:name=".TopLeftLayoutActivity"
+ android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true">
+ <layout android:defaultWidth="240dp"
+ android:defaultHeight="160dp"
+ android:gravity="top|left"
+ android:minWidth="100dp"
+ android:minHeight="80dp"
+ />
+ </activity>
+ <activity android:name=".TopRightLayoutActivity"
+ android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true">
+ <layout android:defaultWidth="25%"
+ android:defaultHeight="35%"
+ android:gravity="top|right"
+ android:minWidth="90dp"
+ android:minHeight="80dp"
+ />
+ </activity>
+ <activity android:name=".BottomLeftLayoutActivity"
+ android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true">
+ <layout android:defaultWidth="25%"
+ android:defaultHeight="35%"
+ android:gravity="bottom|left"
+ android:minWidth="90dp"
+ android:minHeight="80dp"
+ />
+ </activity>
+ <activity android:name=".BottomRightLayoutActivity"
+ android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="true">
+ <layout android:defaultWidth="240dp"
+ android:defaultHeight="160dp"
+ android:gravity="bottom|right"
+ android:minWidth="100dp"
+ android:minHeight="80dp"
+ />
+ </activity>
+ <activity android:name=".TurnScreenOnActivity"
+ android:exported="true"
+ />
+ <activity android:name=".TurnScreenOnDismissKeyguardActivity"
+ android:exported="true"
+ />
+ <activity android:name=".SingleTaskActivity"
+ android:exported="true"
+ android:launchMode="singleTask"
+ />
+ <activity android:name=".SingleInstanceActivity"
+ android:exported="true"
+ android:launchMode="singleInstance"
+ />
+ <activity android:name=".TrampolineActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.NoDisplay"
+ />
+ <activity android:name=".BroadcastReceiverActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ />
+ <activity-alias android:enabled="true"
+ android:exported="true"
+ android:name=".EntryPointAliasActivity"
+ android:targetActivity=".TrampolineActivity" >
+ </activity-alias>
+ <activity android:name=".BottomActivity"
+ android:exported="true"
+ android:theme="@style/NoPreview"
+ />
+ <activity android:name=".TopActivity"
+ android:process=".top_process"
+ android:exported="true"
+ android:theme="@style/NoPreview"
+ />
+ <activity android:name=".TranslucentTopActivity"
+ android:process=".top_process"
+ android:exported="true"
+ android:theme="@style/TranslucentTheme"
+ />
+ <!-- An animation test with an explicitly opaque theme, overriding device defaults, as the
+ animation background being tested is not used in translucent activities. -->
+ <activity android:name=".AnimationTestActivity"
+ android:theme="@style/OpaqueTheme"
+ android:exported="true"
+ />
+ <activity android:name=".VirtualDisplayActivity"
+ android:resizeableActivity="true"
+ android:exported="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ />
+ <activity android:name=".ShowWhenLockedActivity"
+ android:exported="true"
+ />
+ <activity android:name=".ShowWhenLockedWithDialogActivity"
+ android:exported="true"
+ />
+ <activity android:name=".ShowWhenLockedDialogActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.Dialog"
+ />
+ <activity android:name=".ShowWhenLockedTranslucentActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.Translucent"
+ />
+ <activity android:name=".DismissKeyguardActivity"
+ android:exported="true"
+ />
+ <activity android:name=".DismissKeyguardMethodActivity"
+ android:exported="true"
+ />
+ <activity android:name=".WallpaperActivity"
+ android:exported="true"
+ android:theme="@style/WallpaperTheme"
+ />
+ <activity android:name=".KeyguardLockActivity"
+ android:exported="true"
+ />
+ <activity android:name=".LogConfigurationActivity"
+ android:exported="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ />
+ <activity android:name=".PortraitOrientationActivity"
+ android:exported="true"
+ android:screenOrientation="portrait"
+ android:documentLaunchMode="always"
+ />
+ <activity android:name=".LandscapeOrientationActivity"
+ android:exported="true"
+ android:screenOrientation="landscape"
+ android:documentLaunchMode="always"
+ />
+ <activity android:name=".MoveTaskToBackActivity"
+ android:exported="true"
+ android:launchMode="singleInstance"
+ />
+ <activity android:name=".NightModeActivity"
+ android:exported="true"
+ android:configChanges="uiMode"
+ />
+ <activity android:name=".FontScaleActivity"
+ android:exported="true"
+ />
+ <activity android:name=".FontScaleNoRelaunchActivity"
+ android:exported="true"
+ android:configChanges="fontScale"
+ />
+ <receiver
+ android:name=".LaunchBroadcastReceiver"
+ android:enabled="true"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.server.am.LAUNCH_BROADCAST_ACTION"/>
+ </intent-filter>
+ </receiver>
+
+ <activity android:name=".AssistantActivity"
+ android:exported="true" />
+ <activity android:name=".TranslucentAssistantActivity"
+ android:exported="true"
+ android:theme="@style/Theme.Transparent" />
+ <activity android:name=".LaunchAssistantActivityFromSession"
+ android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
+ android:exported="true" />
+ <activity android:name=".LaunchAssistantActivityIntoAssistantStack"
+ android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
+ android:exported="true" />
+
+ <service android:name=".AssistantVoiceInteractionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:exported="true">
+ <meta-data android:name="android.voice_interaction"
+ android:resource="@xml/interaction_service" />
+ <intent-filter>
+ <action android:name="android.service.voice.VoiceInteractionService" />
+ </intent-filter>
+ </service>
+
+ <service android:name=".AssistantVoiceInteractionSessionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:exported="true" />
+
+ <activity android:name=".SplashscreenActivity"
+ android:taskAffinity="nobody.but.SplashscreenActivity"
+ android:theme="@style/SplashscreenTheme"
+ android:exported="true" />
+
+
+ <activity android:name=".SwipeRefreshActivity"
+ android:exported="true" />
+
+ <activity android:name=".NoHistoryActivity"
+ android:noHistory="true"
+ android:exported="true" />
+
+ <activity android:name=".ShowWhenLockedAttrActivity"
+ android:showWhenLocked="true"
+ android:exported="true" />
+
+ <activity android:name=".ShowWhenLockedAttrRemoveAttrActivity"
+ android:showWhenLocked="true"
+ android:exported="true" />
+
+ <activity android:name=".ShowWhenLockedAttrWithDialogActivity"
+ android:showWhenLocked="true"
+ android:exported="true" />
+
+ <activity android:name=".TurnScreenOnAttrActivity"
+ android:turnScreenOn="true"
+ android:exported="true" />
+
+ <activity android:name=".TurnScreenOnShowOnLockActivity"
+ android:showWhenLocked="true"
+ android:turnScreenOn="true"
+ android:exported="true" />
+
+ <activity android:name=".TurnScreenOnAttrRemoveAttrActivity"
+ android:turnScreenOn="true"
+ android:showWhenLocked="true"
+ android:exported="true" />
+
+ <activity android:name=".TurnScreenOnSingleTaskActivity"
+ android:turnScreenOn="true"
+ android:showWhenLocked="true"
+ android:exported="true"
+ android:launchMode="singleTask" />
+
+ <activity android:name=".TurnScreenOnAttrDismissKeyguardActivity"
+ android:turnScreenOn="true"
+ android:exported="true"/>
+
+ <activity android:name=".TurnScreenOnWithRelayoutActivity"
+ android:exported="true"/>
+
+ <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
+ android:exported="true"
+ android:enabled="true"
+ android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.vr.VrListenerService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
+
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/anim/animation_with_background.xml b/tests/framework/base/activitymanager/app/res/anim/animation_with_background.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/anim/animation_with_background.xml
rename to tests/framework/base/activitymanager/app/res/anim/animation_with_background.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/assistant.xml b/tests/framework/base/activitymanager/app/res/layout/assistant.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/assistant.xml
rename to tests/framework/base/activitymanager/app/res/layout/assistant.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/floating.xml b/tests/framework/base/activitymanager/app/res/layout/floating.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/floating.xml
rename to tests/framework/base/activitymanager/app/res/layout/floating.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/font_scale.xml b/tests/framework/base/activitymanager/app/res/layout/font_scale.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/font_scale.xml
rename to tests/framework/base/activitymanager/app/res/layout/font_scale.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/main.xml b/tests/framework/base/activitymanager/app/res/layout/main.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/main.xml
rename to tests/framework/base/activitymanager/app/res/layout/main.xml
diff --git a/tests/framework/base/activitymanager/app/res/layout/resizeable_activity.xml b/tests/framework/base/activitymanager/app/res/layout/resizeable_activity.xml
new file mode 100644
index 0000000..3c249bf
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/res/layout/resizeable_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<android.server.am.LifecycleLogView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</android.server.am.LifecycleLogView>
\ No newline at end of file
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml b/tests/framework/base/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml
rename to tests/framework/base/activitymanager/app/res/layout/tap_to_finish_pip_layout.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/task_overlay.xml b/tests/framework/base/activitymanager/app/res/layout/task_overlay.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/task_overlay.xml
rename to tests/framework/base/activitymanager/app/res/layout/task_overlay.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/translucent.xml b/tests/framework/base/activitymanager/app/res/layout/translucent.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/translucent.xml
rename to tests/framework/base/activitymanager/app/res/layout/translucent.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/virtual_display_layout.xml b/tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/layout/virtual_display_layout.xml
rename to tests/framework/base/activitymanager/app/res/layout/virtual_display_layout.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/colors.xml b/tests/framework/base/activitymanager/app/res/values/colors.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/colors.xml
rename to tests/framework/base/activitymanager/app/res/values/colors.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/styles.xml b/tests/framework/base/activitymanager/app/res/values/styles.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/app/res/values/styles.xml
rename to tests/framework/base/activitymanager/app/res/values/styles.xml
diff --git a/tests/framework/base/activitymanager/app/res/xml/interaction_service.xml b/tests/framework/base/activitymanager/app/res/xml/interaction_service.xml
new file mode 100644
index 0000000..f586037
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="android.server.am.AssistantVoiceInteractionSessionService"
+ android:recognitionService="android.server.am.AssistantVoiceInteractionSessionService"
+ android:supportsAssist="true" />
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AbstractLifecycleLogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AbstractLifecycleLogActivity.java
new file mode 100644
index 0000000..28e6576
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AbstractLifecycleLogActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+public abstract class AbstractLifecycleLogActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ Log.i(getTag(), "onCreate");
+ }
+
+ @Override
+ protected void onStart() {
+ super.onResume();
+ Log.i(getTag(), "onStart");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.i(getTag(), "onResume");
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Log.i(getTag(), "onConfigurationChanged");
+ }
+
+ @Override
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+ super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+ Log.i(getTag(), "onMultiWindowModeChanged");
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+ Configuration newConfig) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+ Log.i(getTag(), "onPictureInPictureModeChanged");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Log.i(getTag(), "onPause");
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.i(getTag(), "onStop");
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Log.i(getTag(), "onDestroy");
+ }
+
+ @Override
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ Log.i(getTag(), "onUserLeaveHint");
+ }
+
+ protected abstract String getTag();
+
+ protected void dumpConfiguration(Configuration config) {
+ Log.i(getTag(), "Configuration: " + config);
+ }
+
+ protected void dumpDisplaySize(Configuration config) {
+ // Dump the display size as seen by this Activity.
+ final WindowManager wm = getSystemService(WindowManager.class);
+ final Display display = wm.getDefaultDisplay();
+ final Point point = new Point();
+ display.getSize(point);
+ final DisplayMetrics metrics = getResources().getDisplayMetrics();
+
+ final String line = "config"
+ + " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp)
+ + " displaySize=" + buildCoordString(point.x, point.y)
+ + " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels)
+ + " smallestScreenWidth=" + config.smallestScreenWidthDp
+ + " densityDpi=" + config.densityDpi
+ + " orientation=" + config.orientation;
+
+ Log.i(getTag(), line);
+ }
+
+ protected static String buildCoordString(int x, int y) {
+ return "(" + x + "," + y + ")";
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AltLaunchingActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AltLaunchingActivity.java
new file mode 100644
index 0000000..3d7aa8a
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AltLaunchingActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * An additional launching activity used for alternating between two activities.
+ */
+public class AltLaunchingActivity extends LaunchingActivity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AlwaysFocusablePipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AlwaysFocusablePipActivity.java
new file mode 100644
index 0000000..0e74977
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AlwaysFocusablePipActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+
+public class AlwaysFocusablePipActivity extends Activity {
+
+ static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
+ final Intent intent = new Intent(caller, AlwaysFocusablePipActivity.class);
+
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK);
+ if (newTask) {
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchBounds(new Rect(0, 0, 500, 500));
+ options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+ caller.startActivity(intent, options.toBundle());
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AnimationTestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AnimationTestActivity.java
new file mode 100644
index 0000000..987ac87
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AnimationTestActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class AnimationTestActivity extends Activity {
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ overridePendingTransition(R.anim.animation_with_background, -1);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java
new file mode 100644
index 0000000..0e0e1fe
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class AssistantActivity extends Activity {
+
+ // Launches the given activity in onResume
+ public static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
+ // Finishes this activity in onResume, this happens after EXTRA_LAUNCH_NEW_TASK
+ public static final String EXTRA_FINISH_SELF = "finish_self";
+ // Attempts to enter picture-in-picture in onResume
+ public static final String EXTRA_ENTER_PIP = "enter_pip";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set the layout
+ setContentView(R.layout.assistant);
+
+ // Launch the new activity if requested
+ if (getIntent().hasExtra(EXTRA_LAUNCH_NEW_TASK)) {
+ Intent i = new Intent();
+ i.setComponent(new ComponentName(this, getPackageName() + "."
+ + getIntent().getStringExtra(EXTRA_LAUNCH_NEW_TASK)));
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(i);
+ }
+
+ // Enter pip if requested
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+ try {
+ enterPictureInPictureMode();
+ } catch (IllegalStateException e) {
+ finish();
+ return;
+ }
+ }
+
+ // Finish this activity if requested
+ if (getIntent().hasExtra(EXTRA_FINISH_SELF)) {
+ finish();
+ }
+ }
+
+ /**
+ * Launches a new instance of the AssistantActivity directly into the assistant stack.
+ */
+ static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
+ final Intent intent = new Intent(caller, AssistantActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
+ caller.startActivity(intent, options.toBundle());
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionService.java b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionService.java
new file mode 100644
index 0000000..b772ce2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionService;
+import android.util.Log;
+
+public class AssistantVoiceInteractionService extends VoiceInteractionService {
+
+ private static final String TAG = AssistantVoiceInteractionService.class.getSimpleName();
+
+ private boolean mReady;
+
+ @Override
+ public void onReady() {
+ super.onReady();
+ mReady = true;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (!isActiveService(this, new ComponentName(this, getClass()))) {
+ Log.wtf(TAG, "**** Not starting AssistantVoiceInteractionService because" +
+ " it is not set as the current voice interaction service");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ if (mReady) {
+ Bundle extras = intent.getExtras() != null ? intent.getExtras() : new Bundle();
+ showSession(extras, 0);
+ }
+ return START_NOT_STICKY;
+ }
+
+ /**
+ * Starts the assistant voice interaction service, which initiates a new session that starts
+ * the assistant activity.
+ */
+ public static void launchAssistantActivity(Context context, Bundle extras) {
+ Intent i = new Intent(context, AssistantVoiceInteractionService.class);
+ if (extras != null) {
+ i.putExtras(extras);
+ }
+ context.startService(i);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionSessionService.java b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionSessionService.java
new file mode 100644
index 0000000..81ed202
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantVoiceInteractionSessionService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class AssistantVoiceInteractionSessionService extends VoiceInteractionSessionService {
+
+ @Override
+ public VoiceInteractionSession onNewSession(Bundle args) {
+ return new VoiceInteractionSession(this) {
+ @Override
+ public void onPrepareShow(Bundle args, int showFlags) {
+ setUiEnabled(false);
+ }
+
+ @Override
+ public void onShow(Bundle args, int showFlags) {
+ Intent i = new Intent(AssistantVoiceInteractionSessionService.this,
+ AssistantActivity.class);
+ if (args != null) {
+ i.putExtras(args);
+ }
+ startAssistantActivity(i);
+ }
+ };
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BottomActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BottomActivity.java
new file mode 100644
index 0000000..de56159
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BottomActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+public class BottomActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = BottomActivity.class.getSimpleName();
+
+ private int mStopDelay;
+ private View mFloatingWindow;
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
+ if (useWallpaper) {
+ setTheme(R.style.WallpaperTheme);
+ }
+ setContentView(R.layout.main);
+
+ // Delayed stop is for simulating a case where resume happens before
+ // activityStopped() is received by AM, and the transition starts without
+ // going through fully stopped state (see b/30255354).
+ // If enabled, we stall onStop() of BottomActivity, open TopActivity but make
+ // it finish before onStop() ends. This will cause BottomActivity to resume before
+ // it notifies AM of activityStopped(). We also add a second window of
+ // TYPE_BASE_APPLICATION, so that the transition animation could start earlier.
+ // Otherwise the main window has to relayout to visible first and the error won't occur.
+ // Note that if the test fails, we shouldn't try to change the app here to make
+ // it pass. The test app is artificially made to simulate an failure case, but
+ // it's not doing anything wrong.
+ mStopDelay = getIntent().getIntExtra("STOP_DELAY", 0);
+ if (mStopDelay > 0) {
+ LayoutInflater inflater = getLayoutInflater();
+ mFloatingWindow = inflater.inflate(R.layout.floating, null);
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+ params.setTitle("Floating");
+ getWindowManager().addView(mFloatingWindow, params);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ Log.d(TAG, "onResume() E");
+ super.onResume();
+
+ if (mStopDelay > 0) {
+ // Refresh floating window
+ Log.d(TAG, "Scheuling invalidate Floating Window in onResume()");
+ mFloatingWindow.invalidate();
+ }
+
+ Log.d(TAG, "onResume() X");
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (mStopDelay > 0) {
+ try {
+ Log.d(TAG, "Stalling onStop() by " + mStopDelay + " ms...");
+ Thread.sleep(mStopDelay);
+ } catch(InterruptedException e) {}
+
+ // Refresh floating window
+ Log.d(TAG, "Scheuling invalidate Floating Window in onStop()");
+ mFloatingWindow.invalidate();
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BottomLeftLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BottomLeftLayoutActivity.java
new file mode 100644
index 0000000..3c4fcb0
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BottomLeftLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class BottomLeftLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BottomRightLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BottomRightLayoutActivity.java
new file mode 100644
index 0000000..8d0e1e2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BottomRightLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class BottomRightLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
new file mode 100644
index 0000000..cd0c745
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.server.am.tools.ActivityLauncher;
+import android.util.Log;
+
+/**
+ * Activity that registers broadcast receiver .
+ */
+public class BroadcastReceiverActivity extends Activity {
+
+ public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
+ private static final String TAG = BroadcastReceiverActivity.class.getSimpleName();
+
+ private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
+
+ registerReceiver(mBroadcastReceiver, broadcastFilter);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mBroadcastReceiver);
+ }
+
+ public class TestBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle extras = intent.getExtras();
+ Log.i(TAG, "onReceive: extras=" + extras);
+
+ if (extras == null) {
+ return;
+ }
+ if (extras.getBoolean("finish")) {
+ finish();
+ }
+ if (extras.getBoolean("moveToBack")) {
+ moveTaskToBack(true);
+ }
+ if (extras.containsKey("orientation")) {
+ setRequestedOrientation(extras.getInt("orientation"));
+ }
+ if (extras.getBoolean("dismissKeyguard")) {
+ getWindow().addFlags(FLAG_DISMISS_KEYGUARD);
+ }
+ if (extras.getBoolean("dismissKeyguardMethod")) {
+ getSystemService(KeyguardManager.class).requestDismissKeyguard(
+ BroadcastReceiverActivity.this, new KeyguardDismissLoggerCallback(context));
+ }
+
+ ActivityLauncher.launchActivityFromExtras(BroadcastReceiverActivity.this, extras);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DialogWhenLargeActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DialogWhenLargeActivity.java
new file mode 100644
index 0000000..0e73939
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DialogWhenLargeActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * Activity with DialogWhenLarge Theme.
+ */
+public class DialogWhenLargeActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = DialogWhenLargeActivity.class.getSimpleName();
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardActivity.java
new file mode 100644
index 0000000..30dc69c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class DismissKeyguardActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
new file mode 100644
index 0000000..e19c4a1
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DismissKeyguardMethodActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+
+public class DismissKeyguardMethodActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+ new KeyguardDismissLoggerCallback(this));
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/DockedActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/DockedActivity.java
new file mode 100644
index 0000000..ce6dbb2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/DockedActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class DockedActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
new file mode 100644
index 0000000..d46ab38
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package android.server.am;
+
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public class FontScaleActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = FontScaleActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ dumpActivityDpi();
+ dumpFontSize();
+ }
+
+ // We're basically ensuring that no matter what happens to the resources underneath the
+ // Activity, any TypedArrays obtained from the pool have the correct DisplayMetrics.
+ protected void dumpFontSize() {
+ try (XmlResourceParser parser = getResources().getXml(R.layout.font_scale)) {
+ //noinspection StatementWithEmptyBody
+ while (parser.next() != XmlPullParser.START_TAG) { }
+
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ TypedArray ta = getTheme().obtainStyledAttributes(attrs,
+ new int[] { android.R.attr.textSize }, 0, 0);
+ try {
+ final int fontPixelSize = ta.getDimensionPixelSize(0, -1);
+ if (fontPixelSize == -1) {
+ throw new AssertionError("android:attr/textSize not found");
+ }
+
+ Log.i(getTag(), "fontPixelSize=" + fontPixelSize);
+ } finally {
+ ta.recycle();
+ }
+ } catch (XmlPullParserException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected void dumpActivityDpi() {
+ final int fontActivityDpi = getResources().getDisplayMetrics().densityDpi;
+ Log.i(getTag(), "fontActivityDpi=" + fontActivityDpi);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.java
new file mode 100644
index 0000000..c7744b0
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package android.server.am;
+
+import android.content.res.Configuration;
+
+public class FontScaleNoRelaunchActivity extends FontScaleActivity {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpActivityDpi();
+ dumpFontSize();
+ }
+
+ @Override
+ protected String getTag() {
+ return FontScaleNoRelaunchActivity.class.getSimpleName();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/FreeformActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/FreeformActivity.java
new file mode 100644
index 0000000..0d60e66
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FreeformActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.graphics.Rect;
+
+public class FreeformActivity extends Activity {
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ final Intent intent = new Intent(this, TestActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchBounds(new Rect(0, 0, 900, 900));
+ this.startActivity(intent, options.toBundle());
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
new file mode 100644
index 0000000..c7113e6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardDismissLoggerCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.KeyguardManager;
+import android.app.KeyguardManager.KeyguardDismissCallback;
+import android.content.Context;
+import android.util.Log;
+
+public class KeyguardDismissLoggerCallback extends KeyguardDismissCallback {
+
+ private final String TAG = "KeyguardDismissLoggerCallback";
+
+ private final Context mContext;
+
+ public KeyguardDismissLoggerCallback(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void onDismissError() {
+ Log.i(TAG, "onDismissError");
+ }
+
+ @Override
+ public void onDismissSucceeded() {
+ if (mContext.getSystemService(KeyguardManager.class).isDeviceLocked()) {
+ // Device is still locked? What a fail. Don't print "onDismissSucceded" such that the
+ // log fails.
+ Log.i(TAG, "dismiss succedded was called but device is still locked.");
+ } else {
+ Log.i(TAG, "onDismissSucceeded");
+ }
+ }
+
+ @Override
+ public void onDismissCancelled() {
+ Log.i(TAG, "onDismissCancelled");
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardLockActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardLockActivity.java
new file mode 100644
index 0000000..f69dc5d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/KeyguardLockActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.KeyguardManager;
+import android.os.Bundle;
+
+public class KeyguardLockActivity extends BroadcastReceiverActivity {
+
+ private KeyguardManager.KeyguardLock mKeyguardLock;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mKeyguardLock = getSystemService(KeyguardManager.class).newKeyguardLock("test");
+ mKeyguardLock.disableKeyguard();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mKeyguardLock.reenableKeyguard();
+ super.onDestroy();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
new file mode 100644
index 0000000..2d7ca6f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LandscapeOrientationActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.res.Configuration;
+
+public class LandscapeOrientationActivity extends AbstractLifecycleLogActivity {
+ @Override
+ protected String getTag() {
+ return "LandscapeOrientationActivity";
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final Configuration config = getResources().getConfiguration();
+ dumpDisplaySize(config);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpDisplaySize(newConfig);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityFromSession.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityFromSession.java
new file mode 100644
index 0000000..c67e9dd
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityFromSession.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class LaunchAssistantActivityFromSession extends Activity {
+ @Override
+ protected void onResume() {
+ super.onResume();
+ AssistantVoiceInteractionService.launchAssistantActivity(this, getIntent().getExtras());
+ finishAndRemoveTask();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.java
new file mode 100644
index 0000000..116bd22
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class LaunchAssistantActivityIntoAssistantStack extends Activity {
+
+ // Launches the translucent assist activity
+ public static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (getIntent().hasExtra(EXTRA_IS_TRANSLUCENT) &&
+ Boolean.valueOf(getIntent().getStringExtra(EXTRA_IS_TRANSLUCENT))) {
+ TranslucentAssistantActivity.launchActivityIntoAssistantStack(this,
+ getIntent().getExtras());
+ } else {
+ AssistantActivity.launchActivityIntoAssistantStack(this, getIntent().getExtras());
+ }
+ finishAndRemoveTask();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
new file mode 100644
index 0000000..01a2fb1
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchBroadcastReceiver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.server.am.tools.ActivityLauncher;
+import android.util.Log;
+
+/** Broadcast receiver that can launch activities. */
+public class LaunchBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ ActivityLauncher.launchActivityFromExtras(context, intent.getExtras());
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException launching activity");
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchEnterPipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchEnterPipActivity.java
new file mode 100644
index 0000000..db1119d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchEnterPipActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+
+public class LaunchEnterPipActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ PipActivity.launchEnterPipActivity(this);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchIntoPinnedStackPipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchIntoPinnedStackPipActivity.java
new file mode 100644
index 0000000..bb97261
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchIntoPinnedStackPipActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class LaunchIntoPinnedStackPipActivity extends Activity {
+ @Override
+ protected void onResume() {
+ super.onResume();
+ AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, true /* newTask */);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchPipOnPipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchPipOnPipActivity.java
new file mode 100644
index 0000000..52263f2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchPipOnPipActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+
+public class LaunchPipOnPipActivity extends Activity {
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this,
+ getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LaunchingActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchingActivity.java
new file mode 100644
index 0000000..814ed60
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchingActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.server.am.tools.ActivityLauncher;
+
+/**
+ * Activity that launches another activities when new intent is received.
+ */
+public class LaunchingActivity extends Activity {
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ ActivityLauncher.launchActivityFromExtras(this, intent.getExtras());
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LifecycleLogView.java b/tests/framework/base/activitymanager/app/src/android/server/am/LifecycleLogView.java
new file mode 100644
index 0000000..e9cc897
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LifecycleLogView.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class LifecycleLogView extends View {
+ private final String TAG = "LifecycleLogView";
+
+ public LifecycleLogView(Context context) {
+ super(context);
+ }
+
+ public LifecycleLogView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LifecycleLogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
+ {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Log.i(TAG, "onConfigurationChanged");
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
new file mode 100644
index 0000000..5ed370e
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LogConfigurationActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.util.Log;
+
+/**
+ * Activity that logs configuration changes.
+ */
+public class LogConfigurationActivity extends Activity {
+
+ private static final String TAG = "LogConfigurationActivity";
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Log.i(TAG, "Configuration changed: " + newConfig.screenWidthDp + ","
+ + newConfig.screenHeightDp);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/MoveTaskToBackActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/MoveTaskToBackActivity.java
new file mode 100644
index 0000000..6c37115
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/MoveTaskToBackActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that finishes itself using "moveTaskToBack".
+ */
+public class MoveTaskToBackActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = MoveTaskToBackActivity.class.getSimpleName();
+
+ private String mFinishPoint;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final Intent intent = getIntent();
+ mFinishPoint = intent.getExtras().getString("finish_point");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (mFinishPoint.equals("on_pause")) {
+ moveTaskToBack(true /* nonRoot */);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (mFinishPoint.equals("on_stop")) {
+ moveTaskToBack(true /* nonRoot */);
+ }
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NightModeActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NightModeActivity.java
new file mode 100644
index 0000000..7c73900
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NightModeActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.os.Bundle;
+
+/** Activity that changes UI mode on creation and handles corresponding configuration change. */
+public class NightModeActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = NightModeActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ UiModeManager uiManager = (UiModeManager) getSystemService(Context.UI_MODE_SERVICE);
+ // Switch the mode two times to make sure it is independent of the current setting.
+ uiManager.setNightMode(UiModeManager.MODE_NIGHT_YES);
+ uiManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NoHistoryActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NoHistoryActivity.java
new file mode 100644
index 0000000..3080fac
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NoHistoryActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * An activity that has the noHistory flag set.
+ */
+public class NoHistoryActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = NoHistoryActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NoRelaunchActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NoRelaunchActivity.java
new file mode 100644
index 0000000..b173f8d
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NoRelaunchActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+public class NoRelaunchActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = NoRelaunchActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/NonResizeableActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/NonResizeableActivity.java
new file mode 100644
index 0000000..e3468e3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/NonResizeableActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+public class NonResizeableActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = NonResizeableActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
new file mode 100644
index 0000000..f5397e6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.PictureInPictureParams;
+import android.content.res.Configuration;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Rational;
+import android.view.WindowManager;
+
+public class PipActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = "PipActivity";
+
+ // Intent action that this activity dynamically registers to enter picture-in-picture
+ private static final String ACTION_ENTER_PIP = "android.server.am.PipActivity.enter_pip";
+ // Intent action that this activity dynamically registers to move itself to the back
+ private static final String ACTION_MOVE_TO_BACK = "android.server.am.PipActivity.move_to_back";
+ // Intent action that this activity dynamically registers to expand itself.
+ // If EXTRA_SET_ASPECT_RATIO_WITH_DELAY is set, it will also attempt to apply the aspect ratio
+ // after a short delay.
+ private static final String ACTION_EXPAND_PIP = "android.server.am.PipActivity.expand_pip";
+ // Intent action that this activity dynamically registers to set requested orientation.
+ // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
+ private static final String ACTION_SET_REQUESTED_ORIENTATION =
+ "android.server.am.PipActivity.set_requested_orientation";
+ // Intent action that will finish this activity
+ private static final String ACTION_FINISH = "android.server.am.PipActivity.finish";
+
+ // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+ private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+ // Calls enterPictureInPicture() on creation
+ private static final String EXTRA_ENTER_PIP = "enter_pip";
+ // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
+ private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
+ "enter_pip_aspect_ratio_numerator";
+ // Used with EXTRA_AUTO_ENTER_PIP, value specifies the aspect ratio to enter PIP with
+ private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
+ "enter_pip_aspect_ratio_denominator";
+ // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
+ private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
+ // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value
+ private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
+ // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
+ // fixed delay
+ private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
+ "set_aspect_ratio_with_delay_numerator";
+ // Calls setPictureInPictureAspectRatio with the aspect ratio specified in the value with a
+ // fixed delay
+ private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
+ "set_aspect_ratio_with_delay_denominator";
+ // Adds a click listener to finish this activity when it is clicked
+ private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+ // Calls requestAutoEnterPictureInPicture() with the value provided
+ private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+ // Starts the activity (component name) provided by the value at the end of onCreate
+ private static final String EXTRA_START_ACTIVITY = "start_activity";
+ // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
+ private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
+ // Calls enterPictureInPicture() again after onPictureInPictureModeChanged(false) is called
+ private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+ // Shows this activity over the keyguard
+ private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
+ // Adds an assertion that we do not ever get onStop() before we enter picture in picture
+ private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+ // The amount to delay to artificially introduce in onPause() (before EXTRA_ENTER_PIP_ON_PAUSE
+ // is processed)
+ private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+ private boolean mEnteredPictureInPicture;
+
+ private Handler mHandler = new Handler();
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null) {
+ switch (intent.getAction()) {
+ case ACTION_ENTER_PIP:
+ enterPictureInPictureMode();
+ break;
+ case ACTION_MOVE_TO_BACK:
+ moveTaskToBack(false /* nonRoot */);
+ break;
+ case ACTION_EXPAND_PIP:
+ // Trigger the activity to expand
+ Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
+ startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ startActivity(startIntent);
+
+ if (intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR)
+ && intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR)) {
+ // Ugly, but required to wait for the startActivity to actually start
+ // the activity...
+ mHandler.postDelayed(() -> {
+ final PictureInPictureParams.Builder builder =
+ new PictureInPictureParams.Builder();
+ builder.setAspectRatio(getAspectRatio(intent,
+ EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR,
+ EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR));
+ setPictureInPictureParams(builder.build());
+ }, 100);
+ }
+ break;
+ case ACTION_SET_REQUESTED_ORIENTATION:
+ setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
+ EXTRA_FIXED_ORIENTATION)));
+ break;
+ case ACTION_FINISH:
+ finish();
+ break;
+ }
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set the fixed orientation if requested
+ if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
+ final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
+ setRequestedOrientation(ori);
+ }
+
+ // Set the window flag to show over the keyguard
+ if (getIntent().hasExtra(EXTRA_SHOW_OVER_KEYGUARD)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+
+ // Enter picture in picture with the given aspect ratio if provided
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
+ && getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR)) {
+ try {
+ final PictureInPictureParams.Builder builder =
+ new PictureInPictureParams.Builder();
+ builder.setAspectRatio(getAspectRatio(getIntent(),
+ EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
+ EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
+ enterPictureInPictureMode(builder.build());
+ } catch (Exception e) {
+ // This call can fail intentionally if the aspect ratio is too extreme
+ }
+ } else {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
+ }
+
+ // We need to wait for either enterPictureInPicture() or requestAutoEnterPictureInPicture()
+ // to be called before setting the aspect ratio
+ if (getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR)
+ && getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR)) {
+ final PictureInPictureParams.Builder builder =
+ new PictureInPictureParams.Builder();
+ builder.setAspectRatio(getAspectRatio(getIntent(),
+ EXTRA_SET_ASPECT_RATIO_NUMERATOR, EXTRA_SET_ASPECT_RATIO_DENOMINATOR));
+ try {
+ setPictureInPictureParams(builder.build());
+ } catch (Exception e) {
+ // This call can fail intentionally if the aspect ratio is too extreme
+ }
+ }
+
+ // Enable tap to finish if necessary
+ if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
+ setContentView(R.layout.tap_to_finish_pip_layout);
+ findViewById(R.id.content).setOnClickListener(v -> {
+ finish();
+ });
+ }
+
+ // Launch a new activity if requested
+ String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
+ if (launchActivityComponent != null) {
+ Intent launchIntent = new Intent();
+ launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
+ startActivity(launchIntent);
+ }
+
+ // Register the broadcast receiver
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_ENTER_PIP);
+ filter.addAction(ACTION_MOVE_TO_BACK);
+ filter.addAction(ACTION_EXPAND_PIP);
+ filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
+ filter.addAction(ACTION_FINISH);
+ registerReceiver(mReceiver, filter);
+
+ // Dump applied display metrics.
+ Configuration config = getResources().getConfiguration();
+ dumpDisplaySize(config);
+ dumpConfiguration(config);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Finish self if requested
+ if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
+ finish();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ // Pause if requested
+ if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
+ SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
+ }
+
+ // Enter PIP on move to background
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
+ Log.w(TAG, "Unexpected onStop() called before entering picture-in-picture");
+ finish();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+
+ // Fail early if the activity state does not match the dispatched state
+ if (isInPictureInPictureMode() != isInPictureInPictureMode) {
+ Log.w(TAG, "Received onPictureInPictureModeChanged mode=" + isInPictureInPictureMode
+ + " activityState=" + isInPictureInPictureMode());
+ finish();
+ }
+
+ // Mark that we've entered picture-in-picture so that we can stop checking for
+ // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
+ if (isInPictureInPictureMode) {
+ mEnteredPictureInPicture = true;
+ }
+
+ if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
+ // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
+ // checking that the stacks ever changed). Therefor, we need to delay here slightly to
+ // allow the tests to verify that the stacks have changed before re-entering.
+ mHandler.postDelayed(() -> {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }, 1000);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpDisplaySize(newConfig);
+ dumpConfiguration(newConfig);
+ }
+
+ /**
+ * Launches a new instance of the PipActivity directly into the pinned stack.
+ */
+ static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
+ final Intent intent = new Intent(caller, PipActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchBounds(bounds);
+ options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+ caller.startActivity(intent, options.toBundle());
+ }
+
+ /**
+ * Launches a new instance of the PipActivity in the same task that will automatically enter
+ * PiP.
+ */
+ static void launchEnterPipActivity(Activity caller) {
+ final Intent intent = new Intent(caller, PipActivity.class);
+ intent.putExtra(EXTRA_ENTER_PIP, "true");
+ intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+ caller.startActivity(intent);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ /**
+ * @return a {@link Rational} aspect ratio from the given intent and extras.
+ */
+ private Rational getAspectRatio(Intent intent, String extraNum, String extraDenom) {
+ return new Rational(
+ Integer.valueOf(intent.getStringExtra(extraNum)),
+ Integer.valueOf(intent.getStringExtra(extraDenom)));
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity2.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity2.java
new file mode 100644
index 0000000..c7c9858
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivity2.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+/**
+ * A secondary activity that has the same behavior as {@link PipActivity}.
+ */
+public class PipActivity2 extends PipActivity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipActivityWithSameAffinity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivityWithSameAffinity.java
new file mode 100644
index 0000000..12ff39f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipActivityWithSameAffinity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.PictureInPictureParams;
+
+/**
+ * An activity that can enter PiP with a fixed affinity to match
+ * {@link TestActivityWithSameAffinity}.
+ */
+public class PipActivityWithSameAffinity extends Activity {
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PipOnStopActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PipOnStopActivity.java
new file mode 100644
index 0000000..264e14c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PipOnStopActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * This activity will try and enter picture in picture when it is stopped.
+ */
+public class PipOnStopActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.tap_to_finish_pip_layout);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ startActivity(new Intent(this, PipActivity.class));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ try {
+ enterPictureInPictureMode();
+ } catch (RuntimeException e) {
+ // Known failure, we expect this call to throw an exception
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
new file mode 100644
index 0000000..a63e3b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/PortraitOrientationActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.res.Configuration;
+
+public class PortraitOrientationActivity extends AbstractLifecycleLogActivity {
+
+ @Override
+ protected String getTag() {
+ return "PortraitOrientationActivity";
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final Configuration config = getResources().getConfiguration();
+ dumpDisplaySize(config);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpDisplaySize(newConfig);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
new file mode 100644
index 0000000..31aa9f2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ResizeableActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.server.am;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+public class ResizeableActivity extends AbstractLifecycleLogActivity {
+ @Override
+ protected String getTag() {
+ return "ResizeableActivity";
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.resizeable_activity);
+ dumpDisplaySize(getResources().getConfiguration());
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpDisplaySize(newConfig);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ResumeWhilePausingActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ResumeWhilePausingActivity.java
new file mode 100644
index 0000000..62fbb8b
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ResumeWhilePausingActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class ResumeWhilePausingActivity extends Activity {
+ // Empty
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedActivity.java
new file mode 100644
index 0000000..1fc7b74
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedActivity extends BroadcastReceiverActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrActivity.java
new file mode 100644
index 0000000..2814361
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class ShowWhenLockedAttrActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = ShowWhenLockedAttrActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrRemoveAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrRemoveAttrActivity.java
new file mode 100644
index 0000000..414d021
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrRemoveAttrActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+
+public class ShowWhenLockedAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = ShowWhenLockedAttrRemoveAttrActivity.class.getSimpleName();
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ setShowWhenLocked(false);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrWithDialogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrWithDialogActivity.java
new file mode 100644
index 0000000..4f9b214
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedAttrWithDialogActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedAttrWithDialogActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ new AlertDialog.Builder(this)
+ .setTitle("Dialog")
+ .show();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedDialogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedDialogActivity.java
new file mode 100644
index 0000000..56eb154
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedDialogActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedDialogActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedTranslucentActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedTranslucentActivity.java
new file mode 100644
index 0000000..6422104
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedTranslucentActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedTranslucentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ setContentView(R.layout.translucent);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedWithDialogActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedWithDialogActivity.java
new file mode 100644
index 0000000..3951532
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/ShowWhenLockedWithDialogActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedWithDialogActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ new AlertDialog.Builder(this)
+ .setTitle("Dialog")
+ .show();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleInstanceActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleInstanceActivity.java
new file mode 100644
index 0000000..2c8b40f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleInstanceActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class SingleInstanceActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskActivity.java
new file mode 100644
index 0000000..d95aa3f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SingleTaskActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class SingleTaskActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.java
new file mode 100644
index 0000000..26992c1
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SlowCreateActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ try {
+ Thread.sleep(2000);
+ } catch(InterruptedException e) {}
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SplashscreenActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SplashscreenActivity.java
new file mode 100644
index 0000000..2d8fbae
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SplashscreenActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Activity that shows a custom splashscreen when being launched.
+ */
+public class SplashscreenActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Make sure splash screen is visible. The test won't take 5 seconds because the condition
+ // such that we can dump the state will trigger much earlier and then the test will just
+ // kill us.
+ SystemClock.sleep(5000);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshActivity.java
new file mode 100644
index 0000000..8e2b781
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.server.am.SwipeRefreshLayout;
+
+
+/**
+ * An activity containing a SwipeRefreshLayout which prevents activity idle.
+ */
+public class SwipeRefreshActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = SwipeRefreshActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(new SwipeRefreshLayout(this));
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshLayout.java b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshLayout.java
new file mode 100644
index 0000000..2b3071c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SwipeRefreshLayout.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * An extension of {@link android.support.v4.widget.SwipeRefreshLayout} that calls
+ * {@link #setRefreshing} during construction, preventing activity idle.
+ */
+public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout {
+ public SwipeRefreshLayout(Context context) {
+ super(context);
+ initialize();
+ }
+
+ public SwipeRefreshLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize();
+ }
+
+ private void initialize() {
+ setRefreshing(true);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TestActivity.java
new file mode 100644
index 0000000..a71ab34
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = TestActivity.class.getSimpleName();
+
+ // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+ private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+
+ // Finishes the activity
+ private static final String ACTION_FINISH_SELF = "android.server.am.TestActivity.finish_self";
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null && intent.getAction().equals(ACTION_FINISH_SELF)) {
+ finish();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Set the fixed orientation if requested
+ if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) {
+ final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
+ setRequestedOrientation(ori);
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ registerReceiver(mReceiver, new IntentFilter(ACTION_FINISH_SELF));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final Configuration config = getResources().getConfiguration();
+ dumpDisplaySize(config);
+ dumpConfiguration(config);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ dumpDisplaySize(newConfig);
+ dumpConfiguration(newConfig);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TestActivityWithSameAffinity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TestActivityWithSameAffinity.java
new file mode 100644
index 0000000..d89d786
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TestActivityWithSameAffinity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.PictureInPictureParams;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivityWithSameAffinity extends TestActivity {
+
+ private static final String TAG = TestActivityWithSameAffinity.class.getSimpleName();
+
+ // Calls enterPictureInPicture() on creation
+ private static final String EXTRA_ENTER_PIP = "enter_pip";
+ // Starts the activity (component name) provided by the value at the end of onCreate
+ private static final String EXTRA_START_ACTIVITY = "start_activity";
+ // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
+ private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // Enter picture in picture if requested
+ if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ }
+
+ // Launch a new activity if requested
+ String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
+ if (launchActivityComponent != null) {
+ Intent launchIntent = new Intent();
+ launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
+ startActivity(launchIntent);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Finish self if requested
+ if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
+ finish();
+ }
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TopActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TopActivity.java
new file mode 100644
index 0000000..7d69ef9
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TopActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.os.Handler;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TopActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = TopActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
+ if (useWallpaper) {
+ setTheme(R.style.WallpaperTheme);
+ }
+
+ final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
+ if (finishDelay > 0) {
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Calling finish()");
+ finish();
+ }
+ }, finishDelay);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TopLeftLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TopLeftLayoutActivity.java
new file mode 100644
index 0000000..1302b22
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TopLeftLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class TopLeftLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TopRightLayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TopRightLayoutActivity.java
new file mode 100644
index 0000000..bb13c01
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TopRightLayoutActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class TopRightLayoutActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TrampolineActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TrampolineActivity.java
new file mode 100644
index 0000000..7391b04
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TrampolineActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.content.Intent;
+
+public class TrampolineActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = TrampolineActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Add a delay here to expose more easily a failure case where the real target
+ // activity is visible before it's launched, because its task is being brought
+ // to foreground. We need to verify that 'am start' is unblocked correctly.
+ try {
+ Thread.sleep(2000);
+ } catch(InterruptedException e) {}
+ Intent intent = new Intent(this, SingleTaskActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK);
+
+ startActivity(intent);
+ finish();
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentActivity.java
new file mode 100644
index 0000000..a83ceaf
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class TranslucentActivity extends Activity {
+
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentAssistantActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentAssistantActivity.java
new file mode 100644
index 0000000..2ad5e17
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentAssistantActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class TranslucentAssistantActivity extends AssistantActivity {
+
+ /**
+ * Launches a new instance of the TranslucentAssistantActivity directly into the assistant
+ * stack.
+ */
+ static void launchActivityIntoAssistantStack(Activity caller, Bundle extras) {
+ final Intent intent = new Intent(caller, TranslucentAssistantActivity.class);
+ intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
+ caller.startActivity(intent, options.toBundle());
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTestActivity.java
new file mode 100644
index 0000000..ba60f67
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTestActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+
+public class TranslucentTestActivity extends TestActivity {
+
+ private static final String TAG = TranslucentTestActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.task_overlay);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTopActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTopActivity.java
new file mode 100644
index 0000000..5d639db
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TranslucentTopActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import android.os.Handler;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TranslucentTopActivity extends AbstractLifecycleLogActivity {
+
+ private static final String TAG = TranslucentTopActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final boolean useWallpaper = getIntent().getBooleanExtra("USE_WALLPAPER", false);
+ if (useWallpaper) {
+ setTheme(R.style.TranslucentWallpaperTheme);
+ }
+
+ final int finishDelay = getIntent().getIntExtra("FINISH_DELAY", 0);
+ if (finishDelay > 0) {
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "Calling finish()");
+ finish();
+ }
+ }, finishDelay);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnActivity.java
new file mode 100644
index 0000000..4334503
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class TurnScreenOnActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrActivity.java
new file mode 100644
index 0000000..f644eb2
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnAttrActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = TurnScreenOnAttrActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
new file mode 100644
index 0000000..acc92b6
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrDismissKeyguardActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.KeyguardManager;
+import android.os.Bundle;
+
+public class TurnScreenOnAttrDismissKeyguardActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = TurnScreenOnAttrDismissKeyguardActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ ((KeyguardManager) getSystemService(KEYGUARD_SERVICE))
+ .requestDismissKeyguard(this, new KeyguardDismissLoggerCallback(this));
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrRemoveAttrActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrRemoveAttrActivity.java
new file mode 100644
index 0000000..9ad7ddd
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnAttrRemoveAttrActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnAttrRemoveAttrActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = TurnScreenOnAttrRemoveAttrActivity.class.getSimpleName();
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ setTurnScreenOn(false);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
new file mode 100644
index 0000000..8f4b947
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnDismissKeyguardActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class TurnScreenOnDismissKeyguardActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+ new KeyguardDismissLoggerCallback(this));
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnShowOnLockActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnShowOnLockActivity.java
new file mode 100644
index 0000000..6113a23
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnShowOnLockActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnShowOnLockActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = TurnScreenOnShowOnLockActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnSingleTaskActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnSingleTaskActivity.java
new file mode 100644
index 0000000..2dddb9b
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnSingleTaskActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+public class TurnScreenOnSingleTaskActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = TurnScreenOnSingleTaskActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnWithRelayoutActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnWithRelayoutActivity.java
new file mode 100644
index 0000000..a1ebbab
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/TurnScreenOnWithRelayoutActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+import android.view.WindowManager;
+
+public class TurnScreenOnWithRelayoutActivity extends AbstractLifecycleLogActivity {
+ private static final String TAG = TurnScreenOnWithRelayoutActivity.class.getSimpleName();
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ // This is to force a relayout, specifically with insets changed. When the insets are
+ // changed, it will trigger a performShow that could turn the screen on.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
new file mode 100644
index 0000000..ade1f74
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Bundle;
+import android.server.am.tools.ActivityLauncher;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+
+/**
+ * Activity that is able to create and destroy a virtual display.
+ */
+public class VirtualDisplayActivity extends Activity implements SurfaceHolder.Callback {
+ private static final String TAG = "VirtualDisplayActivity";
+
+ private static final int DEFAULT_DENSITY_DPI = 160;
+ private static final String KEY_DENSITY_DPI = "density_dpi";
+ private static final String KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD
+ = "can_show_with_insecure_keyguard";
+ private static final String KEY_PUBLIC_DISPLAY = "public_display";
+ private static final String KEY_RESIZE_DISPLAY = "resize_display";
+ private static final String KEY_LAUNCH_TARGET_ACTIVITY = "launch_target_activity";
+ private static final String KEY_COUNT = "count";
+
+ private DisplayManager mDisplayManager;
+
+ // Container for details about a pending virtual display creation request.
+ private static class VirtualDisplayRequest {
+ public final SurfaceView surfaceView;
+ public final Bundle extras;
+
+ public VirtualDisplayRequest(SurfaceView surfaceView, Bundle extras) {
+ this.surfaceView = surfaceView;
+ this.extras = extras;
+ }
+ }
+
+ // Container to hold association between an active virtual display and surface view.
+ private static class VirtualDisplayEntry {
+ public final VirtualDisplay display;
+ public final SurfaceView surfaceView;
+ public final boolean resizeDisplay;
+ public final int density;
+
+ public VirtualDisplayEntry(VirtualDisplay display, SurfaceView surfaceView, int density,
+ boolean resizeDisplay) {
+ this.display = display;
+ this.surfaceView = surfaceView;
+ this.density = density;
+ this.resizeDisplay = resizeDisplay;
+ }
+ }
+
+ private HashMap<Surface, VirtualDisplayRequest> mPendingDisplayRequests;
+ private HashMap<Surface, VirtualDisplayEntry> mVirtualDisplays;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.virtual_display_layout);
+
+ mVirtualDisplays = new HashMap<>();
+ mPendingDisplayRequests = new HashMap<>();
+ mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+
+ String command = extras.getString("command");
+ switch (command) {
+ case "create_display":
+ createVirtualDisplay(extras);
+ break;
+ case "destroy_display":
+ destroyVirtualDisplays();
+ break;
+ case "resize_display":
+ resizeDisplay();
+ break;
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ destroyVirtualDisplays();
+ }
+
+ private void createVirtualDisplay(Bundle extras) {
+ final int requestedCount = extras.getInt(KEY_COUNT, 1);
+ Log.d(TAG, "createVirtualDisplays. requested count:" + requestedCount);
+
+ for (int displayCount = 0; displayCount < requestedCount; ++displayCount) {
+ final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+ final SurfaceView surfaceView = new SurfaceView(this);
+ surfaceView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ surfaceView.getHolder().addCallback(this);
+ mPendingDisplayRequests.put(surfaceView.getHolder().getSurface(),
+ new VirtualDisplayRequest(surfaceView, extras));
+ root.addView(surfaceView);
+ }
+ }
+
+ private void destroyVirtualDisplays() {
+ Log.d(TAG, "destroyVirtualDisplays");
+ final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+
+ for (VirtualDisplayEntry entry : mVirtualDisplays.values()) {
+ Log.d(TAG, "destroying:" + entry.display);
+ entry.display.release();
+ root.removeView(entry.surfaceView);
+ }
+
+ mPendingDisplayRequests.clear();
+ mVirtualDisplays.clear();
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
+ final VirtualDisplayRequest entry =
+ mPendingDisplayRequests.remove(surfaceHolder.getSurface());
+
+ if (entry == null) {
+ return;
+ }
+
+ final int densityDpi = entry.extras.getInt(KEY_DENSITY_DPI, DEFAULT_DENSITY_DPI);
+ final boolean resizeDisplay = entry.extras.getBoolean(KEY_RESIZE_DISPLAY);
+ final String launchActivityName = entry.extras.getString(KEY_LAUNCH_TARGET_ACTIVITY);
+ final Surface surface = surfaceHolder.getSurface();
+
+ // Initially, the surface will not have a set width or height so rely on the parent.
+ // This should be accurate with match parent on both params.
+ final int width = surfaceHolder.getSurfaceFrame().width();
+ final int height = surfaceHolder.getSurfaceFrame().height();
+
+ int flags = 0;
+
+ final boolean canShowWithInsecureKeyguard
+ = entry.extras.getBoolean(KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD);
+ if (canShowWithInsecureKeyguard) {
+ flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+ }
+
+ final boolean publicDisplay = entry.extras.getBoolean(KEY_PUBLIC_DISPLAY);
+ if (publicDisplay) {
+ flags |= VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+ }
+
+ Log.d(TAG, "createVirtualDisplay: " + width + "x" + height + ", dpi: "
+ + densityDpi + ", canShowWithInsecureKeyguard=" + canShowWithInsecureKeyguard
+ + ", publicDisplay=" + publicDisplay);
+ try {
+ VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(
+ "VirtualDisplay" + mVirtualDisplays.size(), width,
+ height, densityDpi, surface, flags);
+ mVirtualDisplays.put(surface,
+ new VirtualDisplayEntry(virtualDisplay, entry.surfaceView, densityDpi,
+ resizeDisplay));
+ if (launchActivityName != null) {
+ final int displayId = virtualDisplay.getDisplay().getDisplayId();
+ Log.d(TAG, "Launch activity after display created: activityName="
+ + launchActivityName + ", displayId=" + displayId);
+ launchActivity(launchActivityName, displayId);
+ }
+ } catch (IllegalArgumentException e) {
+ final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
+ // This is expected when trying to create show-when-locked public display.
+ root.removeView(entry.surfaceView);
+ }
+
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
+ final VirtualDisplayEntry entry = mVirtualDisplays.get(surfaceHolder.getSurface());
+
+ if (entry != null && entry.resizeDisplay) {
+ entry.display.resize(width, height, entry.density);
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+ }
+
+ /** Resize virtual display to half of the surface frame size. */
+ private void resizeDisplay() {
+ final VirtualDisplayEntry vd = (VirtualDisplayEntry) mVirtualDisplays.values().toArray()[0];
+ final SurfaceHolder surfaceHolder = vd.surfaceView.getHolder();
+ vd.display.resize(surfaceHolder.getSurfaceFrame().width() / 2,
+ surfaceHolder.getSurfaceFrame().height() / 2, vd.density);
+ }
+
+ private void launchActivity(String activityName, int displayId) {
+ final Bundle extras = new Bundle();
+ extras.putBoolean("launch_activity", true);
+ extras.putString("target_activity", activityName);
+ extras.putInt("display_id", displayId);
+ ActivityLauncher.launchActivityFromExtras(this, extras);
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/VrTestActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/VrTestActivity.java
new file mode 100644
index 0000000..e163aa8
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/VrTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.cts.verifier.vr.MockVrListenerService;
+
+/**
+ * Activity that is able to create and destroy a virtual display.
+ */
+public class VrTestActivity extends Activity {
+ private static final String TAG = "VrTestActivity";
+ private static final boolean DEBUG = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG) Log.i(TAG, "onCreate called.");
+ super.onCreate(savedInstanceState);
+ try {
+ setVrModeEnabled(true, new ComponentName(this, MockVrListenerService.class));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not set VR mode: " + e);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ if (DEBUG) Log.i(TAG, "onResume called.");
+ super.onResume();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ if (DEBUG) Log.i(TAG, "onWindowFocusChanged called with " + hasFocus);
+ super.onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ protected void onPause() {
+ if (DEBUG) Log.i(TAG, "onPause called.");
+ super.onPause();
+ }
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/WallpaperActivity.java b/tests/framework/base/activitymanager/app/src/android/server/am/WallpaperActivity.java
new file mode 100644
index 0000000..2bbe1b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/WallpaperActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+
+public class WallpaperActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/app/src/android/server/am/tools/ActivityLauncher.java b/tests/framework/base/activitymanager/app/src/android/server/am/tools/ActivityLauncher.java
new file mode 100644
index 0000000..4b35ccf
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/tools/ActivityLauncher.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.tools;
+
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.server.am.TestActivity;
+import android.util.Log;
+
+/** Utility class which contains common code for launching activities. */
+public class ActivityLauncher {
+ private static final String TAG = ActivityLauncher.class.getSimpleName();
+
+ public static void launchActivityFromExtras(final Context context, Bundle extras) {
+ if (extras == null || !extras.getBoolean("launch_activity")) {
+ return;
+ }
+
+ Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
+
+ final Intent newIntent = new Intent();
+ final String targetActivity = extras.getString("target_activity");
+ if (targetActivity != null) {
+ final String extraPackageName = extras.getString("package_name");
+ final String packageName = extraPackageName != null ? extraPackageName
+ : context.getApplicationContext().getPackageName();
+ newIntent.setComponent(new ComponentName(packageName,
+ packageName + "." + targetActivity));
+ } else {
+ newIntent.setClass(context, TestActivity.class);
+ }
+
+ if (extras.getBoolean("launch_to_the_side")) {
+ newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
+ if (extras.getBoolean("random_data")) {
+ final Uri data = new Uri.Builder()
+ .path(String.valueOf(System.currentTimeMillis()))
+ .build();
+ newIntent.setData(data);
+ }
+ }
+ if (extras.getBoolean("multiple_task")) {
+ newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ if (extras.getBoolean("new_task")) {
+ newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ if (extras.getBoolean("reorder_to_front")) {
+ newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
+ }
+
+ ActivityOptions options = null;
+ final int displayId = extras.getInt("display_id", -1);
+ if (displayId != -1) {
+ options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(displayId);
+ newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ try {
+ context.startActivity(newIntent, options != null ? options.toBundle() : null);
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException launching activity");
+ }
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk b/tests/framework/base/activitymanager/appDebuggable/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk
rename to tests/framework/base/activitymanager/appDebuggable/Android.mk
diff --git a/tests/framework/base/activitymanager/appDebuggable/AndroidManifest.xml b/tests/framework/base/activitymanager/appDebuggable/AndroidManifest.xml
new file mode 100644
index 0000000..e8a2452
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDebuggable/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.debuggable">
+
+ <!--
+ * Security policy requires that only debuggable processes can be profiled
+ * which is tested by ActivityManagerAmProfileTests.
+ -->
+ <application android:debuggable="true">
+ <activity android:name=".DebuggableAppActivity"
+ android:resizeableActivity="true"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/DebuggableAppActivity.java b/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/DebuggableAppActivity.java
new file mode 100644
index 0000000..33034b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDebuggable/src/android/server/am/debuggable/DebuggableAppActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.debuggable;
+
+import android.app.Activity;
+
+public class DebuggableAppActivity extends Activity {
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk b/tests/framework/base/activitymanager/appDisplaySize/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk
rename to tests/framework/base/activitymanager/appDisplaySize/Android.mk
diff --git a/tests/framework/base/activitymanager/appDisplaySize/AndroidManifest.xml b/tests/framework/base/activitymanager/appDisplaySize/AndroidManifest.xml
new file mode 100644
index 0000000..a3bef31
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.displaysize">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <!-- Set a ridiculously high value for required smallest width. Hopefully
+ we never have to run CTS on Times Square display. -->
+ <supports-screens android:requiresSmallestWidthDp="100000" />
+
+ <application>
+ <activity android:name=".SmallestWidthActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java
new file mode 100644
index 0000000..1ac4c0f
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am.displaysize;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+
+public class SmallestWidthActivity extends Activity {
+
+ /**
+ * Extra key to launch another activity. The extra value is activity's component name.
+ */
+ private static final String EXTRA_LAUNCH_ANOTHER_ACTIVITY = "launch_another_activity";
+
+ @Override
+ protected void onNewIntent(final Intent intent) {
+ super.onNewIntent(intent);
+
+ if (intent.hasExtra(EXTRA_LAUNCH_ANOTHER_ACTIVITY)) {
+ startActivity(new Intent().setComponent(ComponentName.unflattenFromString(
+ intent.getStringExtra(EXTRA_LAUNCH_ANOTHER_ACTIVITY))));
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk b/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk
new file mode 100644
index 0000000..45942def
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDevicePrereleaseSdkApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml b/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml
new file mode 100644
index 0000000..76cc2b6
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Manually set the compileSdkVersion and codename to simulate being compiled against
+ pre-release O SDK. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.prerelease"
+ android:compileSdkVersion="25"
+ android:compileSdkVersionCodename="O">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+ <application>
+ <activity android:name=".MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/MainActivity.java b/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/MainActivity.java
new file mode 100644
index 0000000..8684faf
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/src/android/server/am/prerelease/MainActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am.prerelease;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk b/tests/framework/base/activitymanager/appSecondUid/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk
rename to tests/framework/base/activitymanager/appSecondUid/Android.mk
diff --git a/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml
new file mode 100644
index 0000000..4d4e98d
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.second">
+
+ <application>
+ <activity
+ android:name=".SecondActivity"
+ android:resizeableActivity="true"
+ android:allowEmbedded="true"
+ android:exported="true" />
+ <activity
+ android:name=".SecondActivityNoEmbedding"
+ android:resizeableActivity="true"
+ android:allowEmbedded="false"
+ android:exported="true" />
+ <receiver
+ android:name=".LaunchBroadcastReceiver"
+ android:enabled="true"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.server.am.second.LAUNCH_BROADCAST_ACTION"/>
+ </intent-filter>
+ </receiver>
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
new file mode 100644
index 0000000..196be3e
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.second;
+
+import android.app.ActivityOptions;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/** Broadcast receiver that can launch activities. */
+public class LaunchBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = LaunchBroadcastReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle extras = intent.getExtras();
+ final Intent newIntent = new Intent();
+
+ final String targetActivity = extras != null ? extras.getString("target_activity") : null;
+ if (targetActivity != null) {
+ String packageName = extras.getString("package_name");
+ newIntent.setComponent(new ComponentName(packageName,
+ packageName + "." + targetActivity));
+ } else {
+ newIntent.setClass(context, SecondActivity.class);
+ }
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ int displayId = extras.getInt("display_id", -1);
+ if (displayId != -1) {
+ options.setLaunchDisplayId(displayId);
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ } else {
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ try {
+ context.startActivity(newIntent, options.toBundle());
+ } catch (SecurityException e) {
+ Log.e(TAG, "SecurityException launching activity");
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivity.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivity.java
new file mode 100644
index 0000000..9607ee2
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.second;
+
+import android.app.Activity;
+
+public class SecondActivity extends Activity {
+}
diff --git a/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivityNoEmbedding.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivityNoEmbedding.java
new file mode 100644
index 0000000..43a967b
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/SecondActivityNoEmbedding.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.second;
+
+import android.app.Activity;
+
+public class SecondActivityNoEmbedding extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk b/tests/framework/base/activitymanager/appThirdUid/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk
rename to tests/framework/base/activitymanager/appThirdUid/Android.mk
diff --git a/tests/framework/base/activitymanager/appThirdUid/AndroidManifest.xml b/tests/framework/base/activitymanager/appThirdUid/AndroidManifest.xml
new file mode 100644
index 0000000..ddd89b0
--- /dev/null
+++ b/tests/framework/base/activitymanager/appThirdUid/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.third">
+
+ <application>
+ <activity android:name=".ThirdActivity"
+ android:resizeableActivity="true"
+ android:allowEmbedded="true"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/ThirdActivity.java b/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/ThirdActivity.java
new file mode 100644
index 0000000..4327d5f
--- /dev/null
+++ b/tests/framework/base/activitymanager/appThirdUid/src/android/server/am/third/ThirdActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.third;
+
+import android.app.Activity;
+
+public class ThirdActivity extends Activity {
+}
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/displayserviceapp/Android.mk
rename to tests/framework/base/activitymanager/displayserviceapp/Android.mk
diff --git a/hostsidetests/services/activityandwindowmanager/displayserviceapp/app/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/app/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/displayserviceapp/app/Android.mk
rename to tests/framework/base/activitymanager/displayserviceapp/app/Android.mk
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml b/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml
new file mode 100644
index 0000000..add9f60
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="android.server.am.displayservice">
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <application android:label="CtsDisplayService">
+ <service android:name=".VirtualDisplayService"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java b/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java
new file mode 100644
index 0000000..bf6b87e
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.displayservice;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Surface;
+
+public class VirtualDisplayService extends Service {
+ private static final String NOTIFICATION_CHANNEL_ID = "cts/VirtualDisplayService";
+ private static final String TAG = "VirtualDisplayService";
+
+ private static final int FOREGROUND_ID = 1;
+
+ private static final int DENSITY = 160;
+ private static final int HEIGHT = 480;
+ private static final int WIDTH = 800;
+
+ private ImageReader mReader;
+ private VirtualDisplay mVirtualDisplay;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ Notification notif = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_dialog_alert)
+ .build();
+ startForeground(FOREGROUND_ID, notif);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String command = intent.getStringExtra("command");
+ Log.d(TAG, "Got command: " + command);
+
+ if ("create".equals(command)) {
+ createVirtualDisplay(intent);
+ } if ("off".equals(command)) {
+ mVirtualDisplay.setSurface(null);
+ } else if ("on".equals(command)) {
+ mVirtualDisplay.setSurface(mReader.getSurface());
+ } else if ("destroy".equals(command)) {
+ destroyVirtualDisplay();
+ stopSelf();
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void createVirtualDisplay(Intent intent) {
+ mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
+
+ final DisplayManager displayManager = getSystemService(DisplayManager.class);
+ final String name = "CtsVirtualDisplay";
+
+ int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+ if (intent.getBooleanExtra("show_content_when_locked", false /* defaultValue */)) {
+ flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+ }
+ mVirtualDisplay = displayManager.createVirtualDisplay(
+ name, WIDTH, HEIGHT, DENSITY, mReader.getSurface(), flags);
+ }
+
+ private void destroyVirtualDisplay() {
+ mVirtualDisplay.release();
+ mReader.close();
+ }
+}
diff --git a/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk
new file mode 100644
index 0000000..613888a
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+ android-support-test
+
+LOCAL_MODULE := cts-display-service-app-util
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java b/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java
new file mode 100644
index 0000000..f541770
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.server.am.displayservice;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DisplayHelper {
+ private static final String VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay";
+ private static final String VIRTUAL_DISPLAY_SERVICE =
+ "android.server.am.displayservice/.VirtualDisplayService";
+ private static final Pattern mDisplayDevicePattern = Pattern.compile(
+ ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
+
+ private boolean mCreated;
+
+ public DisplayHelper() {
+ }
+
+ public void createAndWaitForDisplay(boolean external, boolean requestShowWhenLocked)
+ {
+ StringBuilder command =
+ new StringBuilder("am startfgservice -n " + VIRTUAL_DISPLAY_SERVICE);
+ command.append(" --es command create");
+ if (external) {
+ command.append(" --ez external_display true");
+ }
+ if (requestShowWhenLocked) {
+ command.append(" --ez show_content_when_locked true");
+ }
+ executeShellCommand(command.toString());
+
+ waitForDisplayState(false /* default */, true /* exists */, true /* on */);
+ mCreated = true;
+ }
+
+ public void turnDisplayOff() {
+ executeShellCommand(
+ "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command off");
+ waitForDisplayState(false /* default */, true /* exists */, false /* on */);
+ }
+
+ public void turnDisplayOn() {
+ executeShellCommand(
+ "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command on");
+ waitForDisplayState(false /* default */, true /* exists */, true /* on */);
+ }
+
+ public void releaseDisplay() {
+ if (mCreated) {
+ executeShellCommand(
+ "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command destroy");
+ waitForDisplayState(false /* default */, false /* exists */, true /* on */);
+ }
+ mCreated = false;
+ }
+
+ public static void waitForDefaultDisplayState(boolean wantOn) {
+ waitForDisplayState(true /* default */, true /* exists */, wantOn);
+ }
+
+ public static boolean getDefaultDisplayState() {
+ return getDisplayState(true);
+ }
+
+ private static void waitForDisplayState(boolean defaultDisplay, boolean wantExists, boolean wantOn) {
+ int tries = 0;
+ boolean done = false;
+ do {
+ if (tries > 0) {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ // Oh well
+ }
+ }
+
+ Boolean state = getDisplayState(defaultDisplay);
+ done = (!wantExists && state == null)
+ || (wantExists && state != null && state == wantOn);
+
+ tries++;
+ } while (tries < 10 && !done);
+
+ assertTrue(done);
+ }
+
+ private static Boolean getDisplayState(boolean defaultDisplay) {
+ String dump = executeShellCommand("dumpsys display");
+
+ boolean displayExists = false;
+ boolean displayOn = false;
+ for (String line : dump.split("\\n")) {
+ Matcher matcher = mDisplayDevicePattern.matcher(line);
+ if (matcher.matches()) {
+ if ((defaultDisplay && line.contains("FLAG_DEFAULT_DISPLAY"))
+ || (!defaultDisplay && VIRTUAL_DISPLAY_NAME.equals(matcher.group(1)))) {
+ return "ON".equals(matcher.group(2));
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String executeShellCommand(String command) {
+ try {
+ return SystemUtil
+ .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ } catch (IOException e) {
+ //bubble it up
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
new file mode 100644
index 0000000..116c026
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.server.am.StateLogger.log;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityAndWindowManagerOverrideConfigTests
+ */
+public class ActivityAndWindowManagerOverrideConfigTests extends ActivityManagerTestBase {
+ private static final String TEST_ACTIVITY_NAME = "LogConfigurationActivity";
+
+ private static class ConfigurationChangeObserver {
+ private final Pattern mConfigurationChangedPattern =
+ Pattern.compile("(.+)Configuration changed: (\\d+),(\\d+)");
+
+ private ConfigurationChangeObserver() {
+ }
+
+ private boolean findConfigurationChange(String activityName, String logSeparator)
+ throws InterruptedException {
+ int tries = 0;
+ boolean observedChange = false;
+ while (tries < 5 && !observedChange) {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ log("Looking at logcat");
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ log(line);
+ Matcher matcher = mConfigurationChangedPattern.matcher(line);
+ if (matcher.matches()) {
+ observedChange = true;
+ break;
+ }
+ }
+ tries++;
+ Thread.sleep(500);
+ }
+ return observedChange;
+ }
+ }
+
+ @Test
+ public void testReceiveOverrideConfigFromRelayout() throws Exception {
+ assumeTrue("Device doesn't support freeform. Skipping test.", supportsFreeform());
+
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FREEFORM);
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+ String logSeparator = clearLogcat();
+ resizeActivityTask(TEST_ACTIVITY_NAME, 0, 0, 100, 100);
+ ConfigurationChangeObserver c = new ConfigurationChangeObserver();
+ final boolean reportedSizeAfterResize = c.findConfigurationChange(TEST_ACTIVITY_NAME,
+ logSeparator);
+ assertTrue("Expected to observe configuration change when resizing",
+ reportedSizeAfterResize);
+
+ logSeparator = clearLogcat();
+ rotationSession.set(ROTATION_180);
+ final boolean reportedSizeAfterRotation = c.findConfigurationChange(TEST_ACTIVITY_NAME,
+ logSeparator);
+ assertFalse("Not expected to observe configuration change after flip rotation",
+ reportedSizeAfterRotation);
+ }
+ }
+}
+
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
new file mode 100644
index 0000000..33c5b49
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.server.am.ActivityManagerState.STATE_PAUSED;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.support.test.filters.FlakyTest;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerActivityVisibilityTests
+ */
+public class ActivityManagerActivityVisibilityTests extends ActivityManagerTestBase {
+ private static final String TRANSLUCENT_ACTIVITY = "AlwaysFocusablePipActivity";
+ private static final String PIP_ON_PIP_ACTIVITY = "LaunchPipOnPipActivity";
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
+ private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
+ private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
+ private static final String MOVE_TASK_TO_BACK_ACTIVITY_NAME = "MoveTaskToBackActivity";
+ private static final String SWIPE_REFRESH_ACTIVITY = "SwipeRefreshActivity";
+
+ private static final String NOHISTORY_ACTIVITY = "NoHistoryActivity";
+ private static final String TURN_SCREEN_ON_ATTR_ACTIVITY_NAME = "TurnScreenOnAttrActivity";
+ private static final String TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME = "TurnScreenOnShowOnLockActivity";
+ private static final String TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME = "TurnScreenOnAttrRemoveAttrActivity";
+ private static final String TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME = "TurnScreenOnSingleTaskActivity";
+ private static final String TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY =
+ "TurnScreenOnWithRelayoutActivity";
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ tearDownLockCredentials();
+ }
+
+ @Presubmit
+ @Test
+ public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ executeShellCommand(getAmStartCmdOverHome(PIP_ON_PIP_ACTIVITY));
+ mAmWmState.waitForValidState(PIP_ON_PIP_ACTIVITY);
+ // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
+ // translucent activity.
+ final int stackId = mAmWmState.getAmState().getStackIdByActivityName(PIP_ON_PIP_ACTIVITY);
+
+ assertNotEquals(stackId, INVALID_STACK_ID);
+ executeShellCommand(getMoveToPinnedStackCommand(stackId));
+
+ mAmWmState.computeState(new String[] {PIP_ON_PIP_ACTIVITY, TRANSLUCENT_ACTIVITY});
+ mAmWmState.assertFrontStack("Pinned stack must be the front stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(PIP_ON_PIP_ACTIVITY, true);
+ mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
+ }
+
+ /**
+ * Asserts that the home activity is visible when a translucent activity is launched in the
+ * fullscreen stack over the home activity.
+ */
+ @Test
+ public void testTranslucentActivityOnTopOfHome() throws Exception {
+ assumeTrue(hasHomeScreen());
+
+ launchHomeActivity();
+ launchActivity(TRANSLUCENT_ACTIVITY);
+
+ mAmWmState.computeState( new String[]{TRANSLUCENT_ACTIVITY});
+ mAmWmState.assertFrontStack("Fullscreen stack must be the front stack.",
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ /**
+ * Assert that the home activity is visible if a task that was launched from home is pinned
+ * and also assert the next task in the fullscreen stack isn't visible.
+ */
+ @Presubmit
+ @Test
+ public void testHomeVisibleOnActivityTaskPinned() throws Exception {
+ assumeTrue(supportsPip());
+
+ launchHomeActivity();
+ launchActivity(TEST_ACTIVITY_NAME);
+ launchHomeActivity();
+ launchActivity(TRANSLUCENT_ACTIVITY);
+ final int stackId = mAmWmState.getAmState().getStackIdByActivityName(TRANSLUCENT_ACTIVITY);
+
+ assertNotEquals(stackId, INVALID_STACK_ID);
+ executeShellCommand(getMoveToPinnedStackCommand(stackId));
+
+ mAmWmState.computeState(new String[]{TRANSLUCENT_ACTIVITY});
+
+ mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ @Presubmit
+ @Test
+ public void testTranslucentActivityOverDockedStack() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ launchActivitiesInSplitScreen(DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME);
+ launchActivity(TRANSLUCENT_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(DOCKED_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(TRANSLUCENT_ACTIVITY_NAME).build());
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+ mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY_NAME, true);
+ }
+
+ @Presubmit
+ @Test
+ public void testTurnScreenOnActivity() throws Exception {
+ sleepDevice();
+ launchActivity(TURN_SCREEN_ON_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY_NAME, true);
+ }
+
+ @Presubmit
+ @Test
+ public void testFinishActivityInNonFocusedStack() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ // Launch two activities in docked stack.
+ launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+ getLaunchActivityBuilder().setTargetActivityName(BROADCAST_RECEIVER_ACTIVITY).execute();
+ mAmWmState.computeState(new String[] { BROADCAST_RECEIVER_ACTIVITY });
+ mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+ // Launch something to fullscreen stack to make it focused.
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.computeState(new String[] { TEST_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+ // Finish activity in non-focused (docked) stack.
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+ mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_PAUSED);
+ mAmWmState.waitForAllExitingWindows();
+
+ mAmWmState.computeState(new String[] { LAUNCHING_ACTIVITY });
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+ mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
+ }
+
+ @Test
+ public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+ performFinishActivityWithMoveTaskToBack("on_pause");
+ }
+
+ @Test
+ public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+ performFinishActivityWithMoveTaskToBack("on_stop");
+ }
+
+ private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+ // Make sure home activity is visible.
+ launchHomeActivity();
+ if (hasHomeScreen()) {
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+ }
+
+ // Launch an activity that calls "moveTaskToBack" to finish itself.
+ launchActivity(MOVE_TASK_TO_BACK_ACTIVITY_NAME, "finish_point", finishPoint);
+ mAmWmState.waitForValidState(MOVE_TASK_TO_BACK_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, true);
+
+ // Launch a different activity on top.
+ launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+ mAmWmState.waitForValidState(BROADCAST_RECEIVER_ACTIVITY);
+ mAmWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
+ mAmWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY_NAME, false);
+ mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+
+ // Finish the top-most activity.
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+ //TODO: BUG: MoveTaskToBackActivity returns to the top of the stack when
+ // BroadcastActivity finishes, so homeActivity is not visible afterwards
+
+ // Home must be visible.
+ if (hasHomeScreen()) {
+ mAmWmState.waitForHomeActivityVisible();
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+ }
+ }
+
+ /**
+ * Asserts that launching between reorder to front activities exhibits the correct backstack
+ * behavior.
+ */
+ @Test
+ public void testReorderToFrontBackstack() throws Exception {
+ // Start with home on top
+ launchHomeActivity();
+ if (hasHomeScreen()) {
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+ }
+
+ // Launch the launching activity to the foreground
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ // Launch the alternate launching activity from launching activity with reorder to front.
+ getLaunchActivityBuilder().setTargetActivityName(ALT_LAUNCHING_ACTIVITY)
+ .setReorderToFront(true).execute();
+
+ // Launch the launching activity from the alternate launching activity with reorder to
+ // front.
+ getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY)
+ .setLaunchingActivityName(ALT_LAUNCHING_ACTIVITY).setReorderToFront(true)
+ .execute();
+
+ // Press back
+ pressBackButton();
+
+ mAmWmState.waitForValidState(ALT_LAUNCHING_ACTIVITY);
+
+ // Ensure the alternate launching activity is in focus
+ mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
+ ALT_LAUNCHING_ACTIVITY);
+ }
+
+ /**
+ * Asserts that the activity focus and history is preserved moving between the activity and
+ * home stack.
+ */
+ @Test
+ public void testReorderToFrontChangingStack() throws Exception {
+ // Start with home on top
+ launchHomeActivity();
+ if (hasHomeScreen()) {
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+ }
+
+ // Launch the launching activity to the foreground
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ // Launch the alternate launching activity from launching activity with reorder to front.
+ getLaunchActivityBuilder().setTargetActivityName(ALT_LAUNCHING_ACTIVITY)
+ .setReorderToFront(true).execute();
+
+ // Return home
+ launchHomeActivity();
+ if (hasHomeScreen()) {
+ mAmWmState.assertHomeActivityVisible(true /* visible */);
+ }
+ // Launch the launching activity from the alternate launching activity with reorder to
+ // front.
+
+ // Bring launching activity back to the foreground
+ launchActivity(LAUNCHING_ACTIVITY);
+ mAmWmState.waitForValidState(LAUNCHING_ACTIVITY);
+
+ // Ensure the alternate launching activity is still in focus.
+ mAmWmState.assertFocusedActivity("Alt Launching Activity must be focused",
+ ALT_LAUNCHING_ACTIVITY);
+
+ pressBackButton();
+
+ mAmWmState.waitForValidState(LAUNCHING_ACTIVITY);
+
+ // Ensure launching activity was brought forward.
+ mAmWmState.assertFocusedActivity("Launching Activity must be focused",
+ LAUNCHING_ACTIVITY);
+ }
+
+ /**
+ * Asserts that a nohistory activity is stopped and removed immediately after a resumed activity
+ * above becomes visible and does not idle.
+ */
+ @Test
+ public void testNoHistoryActivityFinishedResumedActivityNotIdle() throws Exception {
+ assumeTrue(hasHomeScreen());
+
+ // Start with home on top
+ launchHomeActivity();
+
+ // Launch no history activity
+ launchActivity(NOHISTORY_ACTIVITY);
+
+ // Launch an activity with a swipe refresh layout configured to prevent idle.
+ launchActivity(SWIPE_REFRESH_ACTIVITY);
+
+ pressBackButton();
+ mAmWmState.waitForHomeActivityVisible();
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ @Test
+ public void testTurnScreenOnAttrNoLockScreen() throws Exception {
+ wakeUpAndRemoveLock();
+ sleepDevice();
+ final String logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_ATTR_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, true);
+ assertTrue(isDisplayOn());
+ assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
+ }
+
+ @Test
+ public void testTurnScreenOnAttrWithLockScreen() throws Exception {
+ assumeTrue(isHandheld());
+
+ setLockCredential();
+ sleepDevice();
+ final String logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_ATTR_ACTIVITY_NAME });
+ assertFalse(isDisplayOn());
+ assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
+ }
+
+ @Test
+ public void testTurnScreenOnShowOnLockAttr() throws Exception {
+ sleepDevice();
+ mAmWmState.waitForAllStoppedActivities();
+ final String logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME, true);
+ assertTrue(isDisplayOn());
+ assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME, logSeparator);
+ }
+
+ @Test
+ public void testTurnScreenOnAttrRemove() throws Exception {
+ sleepDevice();
+ mAmWmState.waitForAllStoppedActivities();
+ String logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] {
+ TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME});
+ assertTrue(isDisplayOn());
+ assertSingleLaunch(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
+
+ sleepDevice();
+ mAmWmState.waitForAllStoppedActivities();
+ logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
+ assertFalse(isDisplayOn());
+ assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
+ }
+
+ @Test
+ @Presubmit
+ public void testTurnScreenOnSingleTask() throws Exception {
+ sleepDevice();
+ String logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, true);
+ assertTrue(isDisplayOn());
+ assertSingleLaunch(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, logSeparator);
+
+ sleepDevice();
+ logSeparator = clearLogcat();
+ launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME });
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, true);
+ assertTrue(isDisplayOn());
+ assertSingleStart(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME, logSeparator);
+ }
+
+ @Test
+ public void testTurnScreenOnActivity_withRelayout() throws Exception {
+ sleepDevice();
+ launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
+ mAmWmState.computeState(new String[] { TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY });
+ mAmWmState.assertVisibility(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, true);
+
+ String logSeparator = clearLogcat();
+ sleepDevice();
+ mAmWmState.waitFor("Waiting for stopped state", () ->
+ lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
+
+ // Ensure there was an actual stop if the waitFor timed out.
+ assertTrue(lifecycleStopOccurred(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY, logSeparator));
+ assertFalse(isDisplayOn());
+ }
+
+ private boolean lifecycleStopOccurred(String activityName, String logSeparator) {
+ ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+ logSeparator);
+ return lifecycleCounts.mStopCount > 0;
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java
new file mode 100644
index 0000000..ea2e2b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerAmProfileTests
+ *
+ * Please talk to Android Studio team first if you want to modify or delete these tests.
+ */
+public class ActivityManagerAmProfileTests extends ActivityManagerTestBase {
+
+ private static final ComponentName DEBUGGABLE_APP_ACTIVITY = ComponentName.createRelative(
+ "android.server.am.debuggable", ".DebuggableAppActivity");
+ private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
+ private static final String FIRST_WORD_NO_STREAMING = "*version\n";
+ private static final String FIRST_WORD_STREAMING = "SLOW"; // Magic word set by runtime.
+
+ private String mReadableFilePath = null;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mReadableFilePath = InstrumentationRegistry.getContext()
+ .getExternalFilesDir(null)
+ .getPath() + "/profile.trace";
+ }
+
+ /**
+ * Test am profile functionality with the following 3 configurable options:
+ * starting the activity before start profiling? yes;
+ * sampling-based profiling? no;
+ * using streaming output mode? no.
+ */
+ @Test
+ public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
+ // am profile start ... , and the same to the following 3 test methods.
+ testProfile(true, false, false);
+ }
+
+ /**
+ * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
+ * only different in the three configuration options.
+ */
+ @Test
+ public void testAmProfileStartNoSamplingStreaming() throws Exception {
+ testProfile(true, false, true);
+ }
+
+ @Test
+ public void testAmProfileStartSamplingNoStreaming() throws Exception {
+ testProfile(true, true, false);
+ }
+
+ @Test
+ public void testAmProfileStartSamplingStreaming() throws Exception {
+ testProfile(true, true, true);
+ }
+
+ @Test
+ public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
+ // am start --start-profiler ..., and the same to the following 3 test methods.
+ testProfile(false, false, false);
+ }
+
+ @Test
+ public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
+ testProfile(false, false, true);
+ }
+
+ @Test
+ public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
+ testProfile(false, true, false);
+ }
+
+ @Test
+ public void testAmStartStartProfilerSamplingStreaming() throws Exception {
+ testProfile(false, true, true);
+ }
+
+ private void testProfile(final boolean startActivityFirst, final boolean sampling,
+ final boolean streaming) throws Exception {
+ if (startActivityFirst) {
+ launchActivity(DEBUGGABLE_APP_ACTIVITY);
+ }
+
+ executeShellCommand(
+ getStartCmd(DEBUGGABLE_APP_ACTIVITY, startActivityFirst, sampling, streaming));
+ // Go to home screen and then warm start the activity to generate some interesting trace.
+ pressHomeButton();
+ launchActivity(DEBUGGABLE_APP_ACTIVITY);
+
+ executeShellCommand(getStopProfileCmd(DEBUGGABLE_APP_ACTIVITY));
+ // Sleep for 0.1 second (100 milliseconds) so the generation of the profiling
+ // file is complete.
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ //ignored
+ }
+ verifyOutputFileFormat(streaming);
+ }
+
+ private static String getStartCmd(final ComponentName activityName,
+ final boolean activityAlreadyStarted, final boolean sampling, final boolean streaming) {
+ final StringBuilder builder = new StringBuilder("am");
+ if (activityAlreadyStarted) {
+ builder.append(" profile start");
+ } else {
+ builder.append(String.format(" start -n %s -W -S --start-profiler %s",
+ activityName.flattenToShortString(), OUTPUT_FILE_PATH));
+ }
+ if (sampling) {
+ builder.append(" --sampling 1000");
+ }
+ if (streaming) {
+ builder.append(" --streaming");
+ }
+ if (activityAlreadyStarted) {
+ builder.append(String.format(
+ " %s %s", activityName.getPackageName(), OUTPUT_FILE_PATH));
+ }
+ return builder.toString();
+ }
+
+ private static String getStopProfileCmd(final ComponentName activityName) {
+ return "am profile stop " + activityName.getPackageName();
+ }
+
+ private void verifyOutputFileFormat(final boolean streaming) throws Exception {
+ // This is a hack. The am service has to write to /data/local/tmp because it doesn't have
+ // access to the sdcard but the test app can't read there
+ executeShellCommand("mv " + OUTPUT_FILE_PATH + " " + mReadableFilePath);
+
+ final String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
+ final byte[] data = readFile(mReadableFilePath);
+ assertTrue("data size=" + data.length, data.length >= expectedFirstWord.length());
+ final String actualFirstWord = new String(data, 0, expectedFirstWord.length());
+ assertEquals("Unexpected first word", expectedFirstWord, actualFirstWord);
+
+ // Clean up.
+ executeShellCommand("rm -f " + OUTPUT_FILE_PATH + " " + mReadableFilePath);
+ }
+
+ private static byte[] readFile(String clientPath) throws Exception {
+ final File file = new File(clientPath);
+ assertTrue("File not found on client: " + clientPath, file.isFile());
+ final int size = (int) file.length();
+ final byte[] bytes = new byte[size];
+ try (final FileInputStream fis = new FileInputStream(file)) {
+ final int readSize = fis.read(bytes, 0, bytes.length);
+ assertEquals("Read all data", bytes.length, readSize);
+ return bytes;
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java
new file mode 100644
index 0000000..6c37afb
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerAmStartOptionsTests
+ */
+public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
+
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String ENTRYPOINT_ACTIVITY_NAME = "EntryPointAliasActivity";
+ private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
+
+ @Test
+ public void testDashD() throws Exception {
+ final String activityComponentName =
+ ActivityManagerTestBase.getActivityComponentName(TEST_ACTIVITY_NAME);
+
+ final String[] waitForActivityRecords = new String[] {activityComponentName};
+
+ // Run at least 2 rounds to verify that -D works with an existing process.
+ // -D could fail in this case if the force stop of process is broken.
+ int prevProcId = -1;
+ for (int i = 0; i < 2; i++) {
+ executeShellCommand("am start -n " + getActivityComponentName(TEST_ACTIVITY_NAME) + " -D");
+
+ mAmWmState.waitForDebuggerWindowVisible(waitForActivityRecords);
+ int procId = mAmWmState.getAmState().getActivityProcId(activityComponentName);
+
+ assertTrue("Invalid ProcId.", procId >= 0);
+ if (i > 0) {
+ assertTrue("Run " + i + " didn't start new proc.", prevProcId != procId);
+ }
+ prevProcId = procId;
+ }
+ }
+
+ @Test
+ public void testDashW_Direct() throws Exception {
+ testDashW(SINGLE_TASK_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void testDashW_Indirect() throws Exception {
+ testDashW(ENTRYPOINT_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+ }
+
+ private void testDashW(final String entryActivity, final String actualActivity)
+ throws Exception {
+ // Test cold start
+ startActivityAndVerifyResult(entryActivity, actualActivity, true);
+
+ // Test warm start
+ pressHomeButton();
+ startActivityAndVerifyResult(entryActivity, actualActivity, false);
+
+ // Test "hot" start (app already in front)
+ startActivityAndVerifyResult(entryActivity, actualActivity, false);
+ }
+
+ private void startActivityAndVerifyResult(final String entryActivity,
+ final String actualActivity, boolean shouldStart) throws Exception {
+ // See TODO below
+ // final String logSeparator = clearLogcat();
+
+ // Pass in different data only when cold starting. This is to make the intent
+ // different in subsequent warm/hot launches, so that the entrypoint alias
+ // activity is always started, but the actual activity is not started again
+ // because of the NEW_TASK and singleTask flags.
+ final String result = executeShellCommand("am start -n " + getActivityComponentName(entryActivity) + " -W"
+ + (shouldStart ? " -d about:blank" : ""));
+
+ // Verify shell command return value
+ verifyShellOutput(result, actualActivity, shouldStart);
+
+ // TODO: Disable logcat check for now.
+ // Logcat of WM or AM tag could be lost (eg. chatty if earlier events generated
+ // too many lines), and make the test look flaky. We need to either use event
+ // log or swith to other mechanisms. Only verify shell output for now, it should
+ // still catch most failures.
+
+ // Verify adb logcat log
+ //verifyLogcat(actualActivity, shouldStart, logSeparator);
+ }
+
+ private static final Pattern sNotStartedWarningPattern = Pattern.compile(
+ "Warning: Activity not started(.*)");
+ private static final Pattern sStatusPattern = Pattern.compile(
+ "Status: (.*)");
+ private static final Pattern sActivityPattern = Pattern.compile(
+ "Activity: (.*)");
+ private static final String sStatusOk = "ok";
+
+ private void verifyShellOutput(
+ final String result, final String activity, boolean shouldStart) {
+ boolean warningFound = false;
+ String status = null;
+ String reportedActivity = null;
+ String componentActivityName = getActivityComponentName(activity);
+
+ final String[] lines = result.split("\\n");
+ // Going from the end of logs to beginning in case if some other activity is started first.
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ Matcher matcher = sNotStartedWarningPattern.matcher(line);
+ if (matcher.matches()) {
+ warningFound = true;
+ continue;
+ }
+ matcher = sStatusPattern.matcher(line);
+ if (matcher.matches()) {
+ status = matcher.group(1);
+ continue;
+ }
+ matcher = sActivityPattern.matcher(line);
+ if (matcher.matches()) {
+ reportedActivity = matcher.group(1);
+ continue;
+ }
+ }
+
+ assertTrue("Status " + status + " is not ok", sStatusOk.equals(status));
+ assertTrue("Reported activity is " + reportedActivity + " not " + componentActivityName,
+ componentActivityName.equals(reportedActivity));
+
+ if (shouldStart && warningFound) {
+ fail("Should start new activity but brought something to front.");
+ } else if (!shouldStart && !warningFound){
+ fail("Should bring existing activity to front but started new activity.");
+ }
+ }
+
+ private static final Pattern sDisplayTimePattern =
+ Pattern.compile("(.+): Displayed (.*): (\\+{0,1})([0-9]+)ms(.*)");
+
+ void verifyLogcat(String actualActivityName, boolean shouldStart, String logSeparator) {
+ int displayCount = 0;
+ String activityName = null;
+
+ for (String line : getDeviceLogsForComponent("ActivityManager", logSeparator)) {
+ line = line.trim();
+
+ Matcher matcher = sDisplayTimePattern.matcher(line);
+ if (matcher.matches()) {
+ activityName = matcher.group(2);
+ // Ignore activitiy displays from other packages, we don't
+ // want some random activity starts to ruin our test.
+ if (!activityName.startsWith("android.server.am")) {
+ continue;
+ }
+ if (!shouldStart) {
+ fail("Shouldn't display anything but displayed " + activityName);
+ }
+ displayCount++;
+ }
+ }
+ final String expectedActivityName = getActivityComponentName(actualActivityName);
+ if (shouldStart) {
+ if (displayCount != 1) {
+ fail("Should display exactly one activity but displayed " + displayCount);
+ } else if (!expectedActivityName.equals(activityName)) {
+ fail("Should display " + expectedActivityName +
+ " but displayed " + activityName);
+ }
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
new file mode 100644
index 0000000..b669db1
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ActivityAndWindowManagersState.dpToPx;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerAppConfigurationTests
+ */
+public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
+ private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String PORTRAIT_ACTIVITY_NAME = "PortraitOrientationActivity";
+ private static final String LANDSCAPE_ACTIVITY_NAME = "LandscapeOrientationActivity";
+ private static final String NIGHT_MODE_ACTIVITY = "NightModeActivity";
+ private static final String DIALOG_WHEN_LARGE_ACTIVITY = "DialogWhenLargeActivity";
+
+ private static final String TRANSLUCENT_ACTIVITY_NAME =
+ "android.server.am.translucentapp.TranslucentLandscapeActivity";
+ private static final ComponentName TRANSLUCENT_LANDSCAPE_ACTIVITY = new ComponentName(
+ "android.server.am.translucentapp", TRANSLUCENT_ACTIVITY_NAME);
+ private static final ComponentName SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY = new ComponentName(
+ "android.server.am.translucentapp26", TRANSLUCENT_ACTIVITY_NAME);
+
+ private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
+
+ private static final int SMALL_WIDTH_DP = 426;
+ private static final int SMALL_HEIGHT_DP = 320;
+
+ /**
+ * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
+ * has an updated size when the Activity is resized from fullscreen to docked state.
+ *
+ * The Activity handles configuration changes, so it will not be restarted between resizes.
+ * On Configuration changes, the Activity logs the Display size and Configuration width
+ * and heights. The values reported in fullscreen should be larger than those reported in
+ * docked state.
+ */
+ @Test
+ public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ String logSeparator = clearLogcat();
+ launchActivity(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ assertSizesAreSane(fullscreenSizes, dockedSizes);
+ }
+
+ /**
+ * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
+ * from docked state to fullscreen (reverse).
+ */
+ @Presubmit
+ @Test
+ @FlakyTest(bugId = 71792393)
+ public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ String logSeparator = clearLogcat();
+ launchActivity(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+ final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ assertSizesAreSane(fullscreenSizes, dockedSizes);
+ }
+
+ /**
+ * Tests whether the Display sizes change when rotating the device.
+ */
+ @Test
+ public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
+ assumeTrue("Skipping test: no rotation support", supportsRotation());
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+
+ final String logSeparator = clearLogcat();
+ launchActivity(RESIZEABLE_ACTIVITY_NAME,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ rotateAndCheckSizes(rotationSession, initialSizes);
+ }
+ }
+
+ /**
+ * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
+ * is in the docked stack.
+ */
+ @Presubmit
+ @Test
+ public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+
+ final String logSeparator = clearLogcat();
+ // Launch our own activity to side in case Recents (or other activity to side) doesn't
+ // support rotation.
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+ // Launch target activity in docked stack.
+ getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
+ final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ rotateAndCheckSizes(rotationSession, initialSizes);
+ }
+ }
+
+ /**
+ * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
+ * is launched to side from docked stack.
+ */
+ @Presubmit
+ @Test
+ public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+
+ final String logSeparator = clearLogcat();
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, RESIZEABLE_ACTIVITY_NAME);
+ final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+
+ rotateAndCheckSizes(rotationSession, initialSizes);
+ }
+ }
+
+ private void rotateAndCheckSizes(RotationSession rotationSession, ReportedSizes prevSizes)
+ throws Exception {
+ final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
+ for (final int rotation : rotations) {
+ final String logSeparator = clearLogcat();
+ final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
+ RESIZEABLE_ACTIVITY_NAME).mStackId;
+ final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
+ rotationSession.set(rotation);
+ final int newDeviceRotation = getDeviceRotation(displayId);
+ if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
+ logE("Got an invalid device rotation value. "
+ + "Continuing the test despite of that, but it is likely to fail.");
+ } else if (rotation != newDeviceRotation) {
+ log("This device doesn't support locked user "
+ + "rotation mode. Not continuing the rotation checks.");
+ return;
+ }
+
+ final ReportedSizes rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+ assertSizesRotate(prevSizes, rotatedSizes);
+ prevSizes = rotatedSizes;
+ }
+ }
+
+ /**
+ * Tests when activity moved from fullscreen stack to docked and back. Activity will be
+ * relaunched twice and it should have same config as initial one.
+ */
+ @Test
+ public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
+ moveActivityFullSplitFull(TEST_ACTIVITY_NAME);
+ }
+
+ /**
+ * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
+ */
+ @Presubmit
+ @Test
+ public void testSameConfigurationFullSplitFullNoRelaunch() throws Exception {
+ moveActivityFullSplitFull(RESIZEABLE_ACTIVITY_NAME);
+ }
+
+ /**
+ * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
+ * Last operation is done in a way which simulates split-screen divider movement maximizing
+ * docked stack size and then moving task to fullscreen stack - the same way it is done when
+ * user long-presses overview/recents button to exit split-screen.
+ * Asserts that initial and final reported sizes in fullscreen stack are the same.
+ */
+ private void moveActivityFullSplitFull(String activityName) throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ // Launch to fullscreen stack and record size.
+ String logSeparator = clearLogcat();
+ launchActivity(activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
+ logSeparator);
+ final Rect displayRect = getDisplayRect(activityName);
+
+ // Move to docked stack.
+ logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, logSeparator);
+ assertSizesAreSane(initialFullscreenSizes, dockedSizes);
+ // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+ // will come up.
+ launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(activityName).build());
+ final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
+ .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+ // Resize docked stack to fullscreen size. This will trigger activity relaunch with
+ // non-empty override configuration corresponding to fullscreen size.
+ logSeparator = clearLogcat();
+ mAm.resizeStack(stack.mStackId, displayRect);
+
+ // Move activity back to fullscreen stack.
+ setActivityTaskWindowingMode(activityName,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
+ logSeparator);
+
+ // After activity configuration was changed twice it must report same size as original one.
+ assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
+ }
+
+ /**
+ * Tests when activity moved from docked stack to fullscreen and back. Activity will be
+ * relaunched twice and it should have same config as initial one.
+ */
+ @Test
+ public void testSameConfigurationSplitFullSplitRelaunch() throws Exception {
+ moveActivitySplitFullSplit(TEST_ACTIVITY_NAME);
+ }
+
+ /**
+ * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
+ */
+ @Test
+ public void testSameConfigurationSplitFullSplitNoRelaunch() throws Exception {
+ moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY_NAME);
+ }
+
+ /**
+ * Tests that an activity with the DialogWhenLarge theme can transform properly when in split
+ * screen.
+ */
+ @Presubmit
+ @Test
+ public void testDialogWhenLargeSplitSmall() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ launchActivity(DIALOG_WHEN_LARGE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
+ .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final WindowManagerState.Display display =
+ mAmWmState.getWmState().getDisplay(stack.mDisplayId);
+ final int density = display.getDpi();
+ final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
+ final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
+
+ mAm.resizeStack(stack.mStackId, new Rect(0, 0, smallWidthPx, smallHeightPx));
+ mAmWmState.waitForValidState(DIALOG_WHEN_LARGE_ACTIVITY,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ /**
+ * Test that device handles consequent requested orientations and displays the activities.
+ */
+ @Presubmit
+ @Test
+ public void testFullscreenAppOrientationRequests() throws Exception {
+ launchActivity(PORTRAIT_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(PORTRAIT_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested portrait orientation",
+ 1 /* portrait */, mAmWmState.getWmState().getLastOrientation());
+
+ launchActivity(LANDSCAPE_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+ launchActivity(PORTRAIT_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(PORTRAIT_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested portrait orientation",
+ 1 /* portrait */, mAmWmState.getWmState().getLastOrientation());
+ }
+
+ @Test
+ public void testNonfullscreenAppOrientationRequests() throws Exception {
+ String logSeparator = clearLogcat();
+ launchActivity(PORTRAIT_ACTIVITY_NAME);
+ final ReportedSizes initialReportedSizes =
+ getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
+ assertEquals("portrait activity should be in portrait",
+ 1 /* portrait */, initialReportedSizes.orientation);
+ logSeparator = clearLogcat();
+
+ launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+ assertEquals("Legacy non-fullscreen activity requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+ // TODO(b/36897968): uncomment once we can suppress unsupported configurations
+ // final ReportedSizes updatedReportedSizes =
+ // getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
+ // assertEquals("portrait activity should not have moved from portrait",
+ // 1 /* portrait */, updatedReportedSizes.orientation);
+ }
+
+ // TODO(b/70870253): This test seems malfunction.
+ // @Test
+ public void testNonFullscreenActivityProhibited() throws Exception {
+ // We do not wait for the activity as it should not launch based on the restrictions around
+ // specifying orientation. We instead start an activity known to launch immediately after
+ // so that we can ensure processing the first activity occurred.
+ launchActivityNoWait(TRANSLUCENT_LANDSCAPE_ACTIVITY);
+ launchActivity(PORTRAIT_ACTIVITY_NAME);
+
+ assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
+ mAmWmState.getAmState().containsActivity(
+ TRANSLUCENT_LANDSCAPE_ACTIVITY.flattenToShortString()));
+ }
+
+ @Test
+ public void testNonFullscreenActivityPermitted() throws Exception {
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+
+ launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+ mAmWmState.assertResumedActivity(
+ "target SDK <= 26 non-fullscreen activity should be allowed to launch",
+ SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
+ assertEquals("non-fullscreen activity requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+ }
+ }
+
+ /**
+ * Test that device handles moving between two tasks with different orientations.
+ */
+ @Test
+ public void testTaskCloseRestoreOrientation() throws Exception {
+ // Start landscape activity.
+ launchActivity(LANDSCAPE_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+ // Start another activity in a different task.
+ launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
+
+ // Request portrait
+ executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
+ mAmWmState.waitForRotation(1);
+
+ // Finish activity
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+ // Verify that activity brought to front is in originally requested orientation.
+ mAmWmState.computeState(
+ new WaitForValidActivityState.Builder(LANDSCAPE_ACTIVITY_NAME).build());
+ assertEquals("Should return to app in landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+ }
+
+ /**
+ * Test that device handles moving between two tasks with different orientations.
+ */
+ @Presubmit
+ @Test
+ @FlakyTest(bugId = 71792393)
+ public void testTaskMoveToBackOrientation() throws Exception {
+ // Start landscape activity.
+ launchActivity(LANDSCAPE_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(LANDSCAPE_ACTIVITY_NAME, true /* visible */);
+ assertEquals("Fullscreen app requested landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+
+ // Start another activity in a different task.
+ launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
+
+ // Request portrait
+ executeShellCommand(getOrientationBroadcast(1 /*portrait*/));
+ mAmWmState.waitForRotation(1);
+
+ // Finish activity
+ executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
+
+ // Verify that activity brought to front is in originally requested orientation.
+ mAmWmState.waitForValidState(LANDSCAPE_ACTIVITY_NAME);
+ assertEquals("Should return to app in landscape orientation",
+ 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
+ }
+
+ /**
+ * Test that device doesn't change device orientation by app request while in multi-window.
+ */
+ @Presubmit
+ @Test
+ public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ requestOrientationInSplitScreen(rotationSession,
+ ROTATION_90 /* portrait */, LANDSCAPE_ACTIVITY_NAME);
+ }
+ }
+
+ /**
+ * Test that device doesn't change device orientation by app request while in multi-window.
+ */
+ @Presubmit
+ @Test
+ public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ requestOrientationInSplitScreen(rotationSession,
+ ROTATION_0 /* landscape */, PORTRAIT_ACTIVITY_NAME);
+ }
+ }
+
+ /**
+ * Rotate the device and launch specified activity in split-screen, checking if orientation
+ * didn't change.
+ */
+ private void requestOrientationInSplitScreen(RotationSession rotationSession, int orientation,
+ String activity) throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ // Set initial orientation.
+ rotationSession.set(orientation);
+
+ // Launch activities that request orientations and check that device doesn't rotate.
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivityName(activity).setMultipleTask(true));
+
+ mAmWmState.assertVisibility(activity, true /* visible */);
+ assertEquals("Split-screen apps shouldn't influence device orientation",
+ orientation, mAmWmState.getWmState().getRotation());
+
+ getLaunchActivityBuilder().setMultipleTask(true).setTargetActivityName(activity).execute();
+ mAmWmState.computeState(new String[] {activity});
+ mAmWmState.assertVisibility(activity, true /* visible */);
+ assertEquals("Split-screen apps shouldn't influence device orientation",
+ orientation, mAmWmState.getWmState().getRotation());
+ }
+
+ /**
+ * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
+ * Asserts that initial and final reported sizes in docked stack are the same.
+ */
+ private void moveActivitySplitFullSplit(String activityName) throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ // Launch to docked stack and record size.
+ String logSeparator = clearLogcat();
+ launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName, logSeparator);
+ // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+ // will come up.
+ launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(activityName).build());
+
+ // Move to fullscreen stack.
+ logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(
+ activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName, logSeparator);
+ assertSizesAreSane(fullscreenSizes, initialDockedSizes);
+
+ // Move activity back to docked stack.
+ logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName, logSeparator);
+
+ // After activity configuration was changed twice it must report same size as original one.
+ assertSizesAreSame(initialDockedSizes, finalDockedSizes);
+ }
+
+ /**
+ * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
+ * have flipped.
+ */
+ private static void assertSizesRotate(ReportedSizes rotationA, ReportedSizes rotationB)
+ throws Exception {
+ assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
+ assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
+ assertEquals(rotationB.displayWidth, rotationB.metricsWidth);
+ assertEquals(rotationB.displayHeight, rotationB.metricsHeight);
+
+ final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight;
+ final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight;
+ assertFalse(beforePortrait == afterPortrait);
+
+ final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp;
+ final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp;
+ assertEquals(beforePortrait, beforeConfigPortrait);
+ assertEquals(afterPortrait, afterConfigPortrait);
+
+ assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp);
+ }
+
+ /**
+ * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
+ * that are smaller than the dockedSizes.
+ */
+ private static void assertSizesAreSane(ReportedSizes fullscreenSizes, ReportedSizes dockedSizes)
+ throws Exception {
+ final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
+ if (portrait) {
+ assertTrue(dockedSizes.displayHeight < fullscreenSizes.displayHeight);
+ assertTrue(dockedSizes.heightDp < fullscreenSizes.heightDp);
+ assertTrue(dockedSizes.metricsHeight < fullscreenSizes.metricsHeight);
+ } else {
+ assertTrue(dockedSizes.displayWidth < fullscreenSizes.displayWidth);
+ assertTrue(dockedSizes.widthDp < fullscreenSizes.widthDp);
+ assertTrue(dockedSizes.metricsWidth < fullscreenSizes.metricsWidth);
+ }
+ }
+
+ /**
+ * Throws an AssertionError if sizes are different.
+ */
+ private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize)
+ throws Exception {
+ assertEquals(firstSize.widthDp, secondSize.widthDp);
+ assertEquals(firstSize.heightDp, secondSize.heightDp);
+ assertEquals(firstSize.displayWidth, secondSize.displayWidth);
+ assertEquals(firstSize.displayHeight, secondSize.displayHeight);
+ assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
+ assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
+ assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
+ }
+
+ private ReportedSizes getActivityDisplaySize(String activityName, String logSeparator)
+ throws Exception {
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(activityName).build());
+ final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
+ assertNotNull(details);
+ return details;
+ }
+
+ private Rect getDisplayRect(String activityName)
+ throws Exception {
+ final String windowName = getWindowName(activityName);
+
+ mAmWmState.computeState(new String[] {activityName});
+ mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+
+ final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
+ mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
+
+ assertEquals("Should have exactly one window state for the activity.", 1,
+ tempWindowList.size());
+
+ WindowManagerState.WindowState windowState = tempWindowList.get(0);
+ assertNotNull("Should have a valid window", windowState);
+
+ WindowManagerState.Display display = mAmWmState.getWmState()
+ .getDisplay(windowState.getDisplayId());
+ assertNotNull("Should be on a display", display);
+
+ return display.getDisplayRect();
+ }
+
+ /**
+ * Test launching an activity which requests specific UI mode during creation.
+ */
+ @Test
+ public void testLaunchWithUiModeChange() throws Exception {
+ // Launch activity that changes UI mode and handles this configuration change.
+ launchActivity(NIGHT_MODE_ACTIVITY);
+ mAmWmState.waitForActivityState(NIGHT_MODE_ACTIVITY, STATE_RESUMED);
+
+ // Check if activity is launched successfully.
+ mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Launched activity should be focused",
+ NIGHT_MODE_ACTIVITY);
+ mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
new file mode 100644
index 0000000..718fe85
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerAssistantStackTests
+ */
+//@Presubmit b/67706642
+public class ActivityManagerAssistantStackTests extends ActivityManagerTestBase {
+
+ private static final String VOICE_INTERACTION_SERVICE = "AssistantVoiceInteractionService";
+
+ private static final String TEST_ACTIVITY = "TestActivity";
+ private static final String ANIMATION_TEST_ACTIVITY = "AnimationTestActivity";
+ private static final String DOCKED_ACTIVITY = "DockedActivity";
+ private static final String ASSISTANT_ACTIVITY = "AssistantActivity";
+ private static final String TRANSLUCENT_ASSISTANT_ACTIVITY = "TranslucentAssistantActivity";
+ private static final String LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION =
+ "LaunchAssistantActivityFromSession";
+ private static final String LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK =
+ "LaunchAssistantActivityIntoAssistantStack";
+ private static final String PIP_ACTIVITY = "PipActivity";
+
+ private static final String EXTRA_ENTER_PIP = "enter_pip";
+ private static final String EXTRA_LAUNCH_NEW_TASK = "launch_new_task";
+ private static final String EXTRA_FINISH_SELF = "finish_self";
+ private static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
+
+ private static final String TEST_ACTIVITY_ACTION_FINISH_SELF =
+ "android.server.am.TestActivity.finish_self";
+
+ @Test
+ @Presubmit
+ public void testLaunchingAssistantActivityIntoAssistantStack() throws Exception {
+ // Enable the assistant and launch an assistant activity
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+ mAmWmState.waitForValidStateWithActivityType(ASSISTANT_ACTIVITY,
+ ACTIVITY_TYPE_ASSISTANT);
+
+ // Ensure that the activity launched in the fullscreen assistant stack
+ assertAssistantStackExists();
+ assertTrue("Expected assistant stack to be fullscreen",
+ mAmWmState.getAmState().getStackByActivityType(
+ ACTIVITY_TYPE_ASSISTANT).isFullscreen());
+ }
+ }
+
+ @FlakyTest(bugId = 69573940)
+ @Presubmit
+ @Test
+ public void testAssistantStackZOrder() throws Exception {
+ assumeTrue(supportsPip());
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Launch a pinned stack task
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ // Dock a task
+ launchActivitiesInSplitScreen(DOCKED_ACTIVITY, TEST_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+ // Enable the assistant and launch an assistant activity, ensure it is on top
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+ mAmWmState.waitForValidStateWithActivityType(ASSISTANT_ACTIVITY,
+ ACTIVITY_TYPE_ASSISTANT);
+ assertAssistantStackExists();
+
+ mAmWmState.assertFrontStack("Pinned stack should be on top.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedStack("Assistant stack should be focused.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ }
+ }
+
+ @Test
+ @Presubmit
+ public void testAssistantStackLaunchNewTask() throws Exception {
+ assertAssistantStackCanLaunchAndReturnFromNewTask();
+ }
+
+ @Test
+ @Presubmit
+ public void testAssistantStackLaunchNewTaskWithDockedStack() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Dock a task
+ launchActivitiesInSplitScreen(DOCKED_ACTIVITY, TEST_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+ assertAssistantStackCanLaunchAndReturnFromNewTask();
+ }
+
+ private void assertAssistantStackCanLaunchAndReturnFromNewTask() throws Exception {
+ final boolean inSplitScreenMode = mAmWmState.getAmState().containsStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+ // Enable the assistant and launch an assistant activity which will launch a new task
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_LAUNCH_NEW_TASK, TEST_ACTIVITY);
+ }
+
+ final int expectedWindowingMode = inSplitScreenMode
+ ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ : WINDOWING_MODE_FULLSCREEN;
+ // Ensure that the fullscreen stack is on top and the test activity is now visible
+ mAmWmState.waitForValidState(TEST_ACTIVITY, expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
+ mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
+ expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
+ expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+
+ // Now, tell it to finish itself and ensure that the assistant stack is brought back forward
+ executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH_SELF);
+ mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ mAmWmState.assertFrontStackActivityType(
+ "Assistant stack should be on top.", ACTIVITY_TYPE_ASSISTANT);
+ mAmWmState.assertFocusedStack("Assistant stack should be focused.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ }
+
+ @Test
+ @Presubmit
+ public void testAssistantStackFinishToPreviousApp() throws Exception {
+ // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
+ // the fullscreen activity is still visible and on top after the assistant activity finishes
+ launchActivity(TEST_ACTIVITY);
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_FINISH_SELF, "true");
+ }
+ mAmWmState.waitForValidState(TEST_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+ mAmWmState.assertFocusedActivity("TestActivity should be resumed", TEST_ACTIVITY);
+ mAmWmState.assertFrontStack("Fullscreen stack should be on top.",
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedStack("Fullscreen stack should be focused.",
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ @Presubmit
+ public void testDisallowEnterPiPFromAssistantStack() throws Exception {
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_ENTER_PIP, "true");
+ }
+ mAmWmState.waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @FlakyTest(bugId = 69573940)
+ @Presubmit
+ @Test
+ public void testTranslucentAssistantActivityStackVisibility() throws Exception {
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ // Go home, launch the assistant and check to see that home is visible
+ removeStacksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ launchHomeActivity();
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_IS_TRANSLUCENT, String.valueOf(true));
+ mAmWmState.waitForValidStateWithActivityType(
+ TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ assertAssistantStackExists();
+ mAmWmState.waitForHomeActivityVisible();
+ if (hasHomeScreen()) {
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ // Launch a fullscreen app and then launch the assistant and check to see that it is
+ // also visible
+ removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+ launchActivity(TEST_ACTIVITY);
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_IS_TRANSLUCENT, String.valueOf(true));
+ mAmWmState.waitForValidStateWithActivityType(
+ TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ assertAssistantStackExists();
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+ // Go home, launch assistant, launch app into fullscreen with activity present, and go back.
+
+ // Ensure home is visible.
+ removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+ launchHomeActivity();
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_IS_TRANSLUCENT, String.valueOf(true), EXTRA_LAUNCH_NEW_TASK,
+ TEST_ACTIVITY);
+ mAmWmState.waitForValidState(TEST_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertHomeActivityVisible(false);
+ pressBackButton();
+ mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ assertAssistantStackExists();
+ mAmWmState.waitForHomeActivityVisible();
+ if (hasHomeScreen()) {
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ // Launch a fullscreen and docked app and then launch the assistant and check to see
+ // that it
+ // is also visible
+ if (supportsSplitScreenMultiWindow()) {
+ removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+ launchActivitiesInSplitScreen(DOCKED_ACTIVITY, TEST_ACTIVITY);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_IS_TRANSLUCENT, String.valueOf(true));
+ mAmWmState.waitForValidStateWithActivityType(
+ TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ assertAssistantStackExists();
+ mAmWmState.assertVisibility(DOCKED_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+ }
+ }
+ }
+
+ @FlakyTest(bugId = 69229402)
+ @Test
+ @Presubmit
+ public void testLaunchIntoSameTask() throws Exception {
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ // Launch the assistant
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+ assertAssistantStackExists();
+ mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
+ mAmWmState.assertFocusedStack("Expected assistant stack focused",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ assertEquals(1, mAmWmState.getAmState().getStackByActivityType(
+ ACTIVITY_TYPE_ASSISTANT).getTasks().size());
+ final int taskId = mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY)
+ .mTaskId;
+
+ // Launch a new fullscreen activity
+ // Using Animation Test Activity because it is opaque on all devices.
+ launchActivity(ANIMATION_TEST_ACTIVITY);
+ mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, false);
+
+ // Launch the assistant again and ensure that it goes into the same task
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+ assertAssistantStackExists();
+ mAmWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
+ mAmWmState.assertFocusedStack("Expected assistant stack focused",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ assertEquals(1, mAmWmState.getAmState().getStackByActivityType(
+ ACTIVITY_TYPE_ASSISTANT).getTasks().size());
+ assertEquals(taskId,
+ mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY).mTaskId);
+
+ }
+ }
+
+ @Test
+ public void testPinnedStackWithAssistant() throws Exception {
+ assumeTrue(supportsPip());
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ try (final AssistantSession assistantSession = new AssistantSession()) {
+ assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+ // Launch a fullscreen activity and a PIP activity, then launch the assistant, and ensure
+
+ // that the test activity is still visible
+ launchActivity(TEST_ACTIVITY);
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_IS_TRANSLUCENT, String.valueOf(true));
+ mAmWmState.waitForValidStateWithActivityType(
+ TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ assertAssistantStackExists();
+ mAmWmState.assertVisibility(TRANSLUCENT_ASSISTANT_ACTIVITY, true);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+ }
+ }
+
+ /**
+ * Asserts that the assistant stack exists.
+ */
+ private void assertAssistantStackExists() throws Exception {
+ mAmWmState.assertContainsStack("Must contain assistant stack.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ }
+
+ /** Helper class to save, set, and restore
+ * {@link Settings.Secure#VOICE_INTERACTION_SERVICE} system preference.
+ */
+ private static class AssistantSession extends SettingsSession<String> {
+ AssistantSession() {
+ super(Settings.Secure.getUriFor(Settings.Secure.VOICE_INTERACTION_SERVICE),
+ Settings.Secure::getString, Settings.Secure::putString);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
new file mode 100644
index 0000000..a6760c3
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerConfigChangeTests
+ */
+public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+
+ private static final String FONT_SCALE_ACTIVITY_NAME = "FontScaleActivity";
+ private static final String FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME =
+ "FontScaleNoRelaunchActivity";
+
+ private static final float EXPECTED_FONT_SIZE_SP = 10.0f;
+
+ @Test
+ public void testRotation90Relaunch() throws Exception{
+ // Should relaunch on every rotation and receive no onConfigurationChanged()
+ testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
+ }
+
+ @Test
+ public void testRotation90NoRelaunch() throws Exception {
+ // Should receive onConfigurationChanged() on every rotation and no relaunch
+ testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
+ }
+
+ @Test
+ public void testRotation180Relaunch() throws Exception {
+ // Should receive nothing
+ testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
+ }
+
+ @Test
+ public void testRotation180NoRelaunch() throws Exception {
+ // Should receive nothing
+ testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
+ }
+
+ @Presubmit
+ @Test
+ public void testChangeFontScaleRelaunch() throws Exception {
+ // Should relaunch and receive no onConfigurationChanged()
+ testChangeFontScale(FONT_SCALE_ACTIVITY_NAME, true /* relaunch */);
+ }
+
+ @Presubmit
+ @Test
+ public void testChangeFontScaleNoRelaunch() throws Exception {
+ // Should receive onConfigurationChanged() and no relaunch
+ testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME, false /* relaunch */);
+ }
+
+ private void testRotation(String activityName, int rotationStep, int numRelaunch,
+ int numConfigChange) throws Exception {
+ launchActivity(activityName);
+
+ final String[] waitForActivitiesVisible = new String[] {activityName};
+ mAmWmState.computeState(waitForActivitiesVisible);
+
+ final int initialRotation = 4 - rotationStep;
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(initialRotation);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
+ activityName).mStackId;
+ final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
+ final int newDeviceRotation = getDeviceRotation(displayId);
+ if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
+ logE("Got an invalid device rotation value. "
+ + "Continuing the test despite of that, but it is likely to fail.");
+ } else if (newDeviceRotation != initialRotation) {
+ log("This device doesn't support user rotation "
+ + "mode. Not continuing the rotation checks.");
+ return;
+ }
+
+ for (int rotation = 0; rotation < 4; rotation += rotationStep) {
+ final String logSeparator = clearLogcat();
+ rotationSession.set(rotation);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
+ logSeparator);
+ }
+ }
+ }
+
+ /** Helper class to save, set, and restore font_scale preferences. */
+ private static class FontScaleSession extends SettingsSession<Float> {
+ FontScaleSession() {
+ super(Settings.System.getUriFor(Settings.System.FONT_SCALE),
+ Settings.System::getFloat,
+ Settings.System::putFloat);
+ }
+ }
+
+ private void testChangeFontScale(
+ String activityName, boolean relaunch) throws Exception {
+ try (final FontScaleSession fontScaleSession = new FontScaleSession()) {
+ fontScaleSession.set(1.0f);
+ String logSeparator = clearLogcat();
+ launchActivity(activityName);
+ final String[] waitForActivitiesVisible = new String[]{activityName};
+ mAmWmState.computeState(waitForActivitiesVisible);
+
+ final int densityDpi = getActivityDensityDpi(activityName, logSeparator);
+
+ for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+ logSeparator = clearLogcat();
+ fontScaleSession.set(fontScale);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1,
+ logSeparator);
+
+ // Verify that the display metrics are updated, and therefore the text size is also
+ // updated accordingly.
+ assertExpectedFontPixelSize(activityName,
+ scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi),
+ logSeparator);
+ }
+ }
+ }
+
+ /**
+ * Test updating application info when app is running. An activity with matching package name
+ * must be recreated and its asset sequence number must be incremented.
+ */
+ @Test
+ public void testUpdateApplicationInfo() throws Exception {
+ final String firstLogSeparator = clearLogcat();
+
+ // Launch an activity that prints applied config.
+ launchActivity(TEST_ACTIVITY_NAME);
+ final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, firstLogSeparator);
+
+ final String logSeparator = clearLogcat();
+ // Update package info.
+ executeShellCommand("am update-appinfo all " + componentName);
+ mAmWmState.waitForWithAmState((amState) -> {
+ // Wait for activity to be resumed and asset seq number to be updated.
+ try {
+ return readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator) == assetSeq + 1
+ && amState.hasActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
+ } catch (Exception e) {
+ logE("Error waiting for valid state: " + e.getMessage());
+ return false;
+ }
+ }, "Waiting asset sequence number to be updated and for activity to be resumed.");
+
+ // Check if activity is relaunched and asset seq is updated.
+ assertRelaunchOrConfigChanged(TEST_ACTIVITY_NAME, 1 /* numRelaunch */,
+ 0 /* numConfigChange */, logSeparator);
+ final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator);
+ assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
+ }
+
+ private static final Pattern sConfigurationPattern = Pattern.compile(
+ "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
+
+ /** Read asset sequence number in last applied configuration from logs. */
+ private int readAssetSeqNumber(String activityName, String logSeparator) throws Exception {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ final Matcher matcher = sConfigurationPattern.matcher(line);
+ if (matcher.matches()) {
+ final String assetSeqNumber = matcher.group(3);
+ try {
+ return Integer.valueOf(assetSeqNumber);
+ } catch (NumberFormatException e) {
+ // Ignore, asset seq number is not printed when not set.
+ }
+ }
+ }
+ return 0;
+ }
+
+ // Calculate the scaled pixel size just like the device is supposed to.
+ private static int scaledPixelsToPixels(float sp, float fontScale, int densityDpi) {
+ final int DEFAULT_DENSITY = 160;
+ float f = densityDpi * (1.0f / DEFAULT_DENSITY) * fontScale * sp;
+ return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
+ }
+
+ private static Pattern sDeviceDensityPattern = Pattern.compile("^(.+): fontActivityDpi=(.+)$");
+
+ private int getActivityDensityDpi(String activityName, String logSeparator) throws Exception {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ final Matcher matcher = sDeviceDensityPattern.matcher(line);
+ if (matcher.matches()) {
+ return Integer.parseInt(matcher.group(2));
+ }
+ }
+ fail("No fontActivityDpi reported from activity " + activityName);
+ return -1;
+ }
+
+ private static final Pattern sFontSizePattern = Pattern.compile("^(.+): fontPixelSize=(.+)$");
+
+ /** Read the font size in the last log line. */
+ private void assertExpectedFontPixelSize(String activityName, int fontPixelSize,
+ String logSeparator) throws Exception {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ final Matcher matcher = sFontSizePattern.matcher(line);
+ if (matcher.matches()) {
+ assertEquals("Expected font pixel size does not match", fontPixelSize,
+ Integer.parseInt(matcher.group(2)));
+ return;
+ }
+ }
+ fail("No fontPixelSize reported from activity " + activityName);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
new file mode 100644
index 0000000..4d6827e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static org.junit.Assume.assumeTrue;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Display tests that require a keyguard.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayKeyguardTests
+ */
+public class ActivityManagerDisplayKeyguardTests extends ActivityManagerDisplayTestBase {
+ private static final String DISMISS_KEYGUARD_ACTIVITY = "DismissKeyguardActivity";
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(supportsMultiDisplay());
+ assumeTrue(isHandheld());
+
+ setLockDisabled(false);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ tearDownLockCredentials();
+ }
+
+ /**
+ * Tests whether a FLAG_DISMISS_KEYGUARD activity on a secondary display is visible (for an
+ * insecure keyguard).
+ */
+ @Test
+ public void testDismissKeyguardActivity_secondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
new file mode 100644
index 0000000..6f9e9d0
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Display tests that require a locked keyguard.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayLockedKeyguardTests
+ */
+public class ActivityManagerDisplayLockedKeyguardTests extends ActivityManagerDisplayTestBase {
+
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
+ private static final String DISMISS_KEYGUARD_ACTIVITY = "DismissKeyguardActivity";
+ private static final String SHOW_WHEN_LOCKED_ACTIVITY = "ShowWhenLockedActivity";
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(supportsMultiDisplay());
+ assumeTrue(isHandheld());
+
+ setLockCredential();
+ }
+
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ tearDownLockCredentials();
+ super.tearDown();
+ }
+
+ /**
+ * Test that virtual display content is hidden when device is locked.
+ */
+ @Test
+ public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new usual virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+
+ // Lock the device.
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_STOPPED);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false /* visible */);
+
+ // Unlock and check if visibility is back.
+ unlockDeviceWithCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+ }
+ }
+
+ /**
+ * Tests whether a FLAG_DISMISS_KEYGUARD activity on a secondary display dismisses the keyguard.
+ */
+ @Test
+ public void testDismissKeyguard_secondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ }
+ }
+
+ @Test
+ public void testDismissKeyguard_whileOccluded_secondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState(SHOW_WHEN_LOCKED_ACTIVITY));
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
new file mode 100644
index 0000000..09d793f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.provider.Settings;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.settings.SettingsSession;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Size;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Base class for ActivityManager display tests.
+ *
+ * @see ActivityManagerDisplayTests
+ * @see ActivityManagerDisplayLockedKeyguardTests
+ */
+public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
+ private static final String WM_SIZE = "wm size";
+ private static final String WM_DENSITY = "wm density";
+
+ static final int CUSTOM_DENSITY_DPI = 222;
+
+ private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
+ private static final int INVALID_DENSITY_DPI = -1;
+
+ ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
+ for (ActivityDisplay display : displays) {
+ if (display.mId == displayId) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ /** Return the display state with width, height, dpi. Always not default display. */
+ ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int width, int height,
+ int dpi) {
+ for (ActivityDisplay display : displays) {
+ if (display.mId == DEFAULT_DISPLAY_ID) {
+ continue;
+ }
+ final Configuration config = display.mFullConfiguration;
+ if (config.densityDpi == dpi && config.screenWidthDp == width
+ && config.screenHeightDp == height) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ List<ActivityDisplay> getDisplaysStates() {
+ mAmWmState.getAmState().computeState();
+ return mAmWmState.getAmState().getDisplays();
+ }
+
+ /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
+ List<ActivityDisplay> findNewDisplayStates(List<ActivityDisplay> oldDisplays,
+ List<ActivityDisplay> newDisplays) {
+ final ArrayList<ActivityDisplay> result = new ArrayList<>();
+
+ for (ActivityDisplay newDisplay : newDisplays) {
+ if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
+ result.add(newDisplay);
+ }
+ }
+
+ return result;
+ }
+
+ static class ReportedDisplayMetrics {
+ private static final String WM_SIZE = "wm size";
+ private static final String WM_DENSITY = "wm density";
+ private static final Pattern PHYSICAL_SIZE =
+ Pattern.compile("Physical size: (\\d+)x(\\d+)");
+ private static final Pattern OVERRIDE_SIZE =
+ Pattern.compile("Override size: (\\d+)x(\\d+)");
+ private static final Pattern PHYSICAL_DENSITY =
+ Pattern.compile("Physical density: (\\d+)");
+ private static final Pattern OVERRIDE_DENSITY =
+ Pattern.compile("Override density: (\\d+)");
+
+ @NonNull
+ final Size physicalSize;
+ final int physicalDensity;
+
+ @Nullable
+ final Size overrideSize;
+ @Nullable
+ final Integer overrideDensity;
+
+ /** Get physical and override display metrics from WM. */
+ static ReportedDisplayMetrics getDisplayMetrics() throws Exception {
+ return new ReportedDisplayMetrics(
+ executeShellCommand(WM_SIZE) + executeShellCommand(WM_DENSITY));
+ }
+
+ void setDisplayMetrics(final Size size, final int density) {
+ setSize(size);
+ setDensity(density);
+ }
+
+ void restoreDisplayMetrics() {
+ if (overrideSize != null) {
+ setSize(overrideSize);
+ } else {
+ executeShellCommand(WM_SIZE + " reset");
+ }
+ if (overrideDensity != null) {
+ setDensity(overrideDensity);
+ } else {
+ executeShellCommand(WM_DENSITY + " reset");
+ }
+ }
+
+ private void setSize(final Size size) {
+ executeShellCommand(WM_SIZE + " " + size.getWidth() + "x" + size.getHeight());
+ }
+
+ private void setDensity(final int density) {
+ executeShellCommand(WM_DENSITY + " " + density);
+ }
+
+ /** Get display size that WM operates with. */
+ Size getSize() {
+ return overrideSize != null ? overrideSize : physicalSize;
+ }
+
+ /** Get density that WM operates with. */
+ int getDensity() {
+ return overrideDensity != null ? overrideDensity : physicalDensity;
+ }
+
+ private ReportedDisplayMetrics(final String lines) throws Exception {
+ Matcher matcher = PHYSICAL_SIZE.matcher(lines);
+ assertTrue("Physical display size must be reported", matcher.find());
+ log(matcher.group());
+ physicalSize = new Size(
+ Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
+
+ matcher = PHYSICAL_DENSITY.matcher(lines);
+ assertTrue("Physical display density must be reported", matcher.find());
+ log(matcher.group());
+ physicalDensity = Integer.parseInt(matcher.group(1));
+
+ matcher = OVERRIDE_SIZE.matcher(lines);
+ if (matcher.find()) {
+ log(matcher.group());
+ overrideSize = new Size(
+ Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
+ } else {
+ overrideSize = null;
+ }
+
+ matcher = OVERRIDE_DENSITY.matcher(lines);
+ if (matcher.find()) {
+ log(matcher.group());
+ overrideDensity = Integer.parseInt(matcher.group(1));
+ } else {
+ overrideDensity = null;
+ }
+ }
+ }
+
+ protected class VirtualDisplaySession implements AutoCloseable {
+ private int mDensityDpi = CUSTOM_DENSITY_DPI;
+ private boolean mLaunchInSplitScreen = false;
+ private boolean mCanShowWithInsecureKeyguard = false;
+ private boolean mPublicDisplay = false;
+ private boolean mResizeDisplay = true;
+ private String mLaunchActivity = null;
+ private boolean mSimulateDisplay = false;
+ private boolean mMustBeCreated = true;
+
+ private boolean mVirtualDisplayCreated = false;
+ private final OverlayDisplayDevicesSession mOverlayDisplayDeviceSession =
+ new OverlayDisplayDevicesSession();
+
+ public VirtualDisplaySession setDensityDpi(int densityDpi) {
+ mDensityDpi = densityDpi;
+ return this;
+ }
+
+ public VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
+ mLaunchInSplitScreen = launchInSplitScreen;
+ return this;
+ }
+
+ public VirtualDisplaySession setCanShowWithInsecureKeyguard(
+ boolean canShowWithInsecureKeyguard) {
+ mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
+ return this;
+ }
+
+ public VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
+ mPublicDisplay = publicDisplay;
+ return this;
+ }
+
+ public VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
+ mResizeDisplay = resizeDisplay;
+ return this;
+ }
+
+ public VirtualDisplaySession setLaunchActivity(String launchActivity) {
+ mLaunchActivity = launchActivity;
+ return this;
+ }
+
+ public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
+ mSimulateDisplay = simulateDisplay;
+ return this;
+ }
+
+ public VirtualDisplaySession setMustBeCreated(boolean mustBeCreated) {
+ mMustBeCreated = mustBeCreated;
+ return this;
+ }
+
+ @Nullable
+ public ActivityDisplay createDisplay() throws Exception {
+ return createDisplays(1).stream().findFirst().orElse(null);
+ }
+
+ @NonNull
+ public List<ActivityDisplay> createDisplays(int count) throws Exception {
+ if (mSimulateDisplay) {
+ return simulateDisplay();
+ } else {
+ return createVirtualDisplays(count);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ mOverlayDisplayDeviceSession.close();
+ if (mVirtualDisplayCreated) {
+ destroyVirtualDisplays();
+ mVirtualDisplayCreated = false;
+ }
+ }
+
+ /**
+ * Simulate new display.
+ * <pre>
+ * <code>mDensityDpi</code> provide custom density for the display.
+ * </pre>
+ * @return {@link ActivityDisplay} of newly created display.
+ */
+ private List<ActivityDisplay> simulateDisplay() throws Exception {
+ final List<ActivityDisplay> originalDs = getDisplaysStates();
+
+ // Create virtual display with custom density dpi.
+ mOverlayDisplayDeviceSession.set("1024x768/" + mDensityDpi);
+
+ return assertAndGetNewDisplays(1, originalDs);
+ }
+
+ /**
+ * Create new virtual display.
+ * <pre>
+ * <code>mDensityDpi</code> provide custom density for the display.
+ * <code>mLaunchInSplitScreen</code> start {@link VirtualDisplayActivity} to side from
+ * {@link LaunchingActivity} on primary display.
+ * <code>mCanShowWithInsecureKeyguard</code> allow showing content when device is
+ * showing an insecure keyguard.
+ * <code>mMustBeCreated</code> should assert if the display was or wasn't created.
+ * <code>mPublicDisplay</code> make display public.
+ * <code>mResizeDisplay</code> should resize display when surface size changes.
+ * <code>LaunchActivity</code> should launch test activity immediately after display
+ * creation.
+ * </pre>
+ * @param displayCount number of displays to be created.
+ * @return A list of {@link ActivityDisplay} that represent newly created displays.
+ * @throws Exception
+ */
+ private List<ActivityDisplay> createVirtualDisplays(int displayCount) throws Exception {
+ // Start an activity that is able to create virtual displays.
+ if (mLaunchInSplitScreen) {
+ getLaunchActivityBuilder()
+ .setToSide(true)
+ .setTargetActivityName(VIRTUAL_DISPLAY_ACTIVITY)
+ .execute();
+ } else {
+ launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
+ }
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+ final List<ActivityDisplay> originalDS = getDisplaysStates();
+
+ // Create virtual display with custom density dpi.
+ final StringBuilder createVirtualDisplayCommand = new StringBuilder(
+ getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
+ .append(" -f 0x20000000")
+ .append(" --es command create_display");
+ if (mDensityDpi != INVALID_DENSITY_DPI) {
+ createVirtualDisplayCommand
+ .append(" --ei density_dpi ")
+ .append(mDensityDpi);
+ }
+ createVirtualDisplayCommand.append(" --ei count ").append(displayCount)
+ .append(" --ez can_show_with_insecure_keyguard ")
+ .append(mCanShowWithInsecureKeyguard)
+ .append(" --ez public_display ").append(mPublicDisplay)
+ .append(" --ez resize_display ").append(mResizeDisplay);
+ if (mLaunchActivity != null) {
+ createVirtualDisplayCommand
+ .append(" --es launch_target_activity ")
+ .append(mLaunchActivity);
+ }
+ executeShellCommand(createVirtualDisplayCommand.toString());
+ mVirtualDisplayCreated = true;
+
+ return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
+ }
+
+ /**
+ * Destroy existing virtual display.
+ */
+ void destroyVirtualDisplays() throws Exception {
+ final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
+ + " -f 0x20000000"
+ + " --es command destroy_display";
+ executeShellCommand(destroyVirtualDisplayCommand);
+ }
+
+ /**
+ * Wait for desired number of displays to be created and get their properties.
+ * @param newDisplayCount expected display count, -1 if display should not be created.
+ * @param originalDS display states before creation of new display(s).
+ * @return list of new displays, empty list if no new display is created.
+ */
+ private List<ActivityDisplay> assertAndGetNewDisplays(int newDisplayCount,
+ List<ActivityDisplay> originalDS) throws Exception {
+ final int originalDisplayCount = originalDS.size();
+
+ // Wait for the display(s) to be created and get configurations.
+ final List<ActivityDisplay> ds = getDisplayStateAfterChange(
+ originalDisplayCount + newDisplayCount);
+ if (newDisplayCount != -1) {
+ assertEquals("New virtual display(s) must be created",
+ originalDisplayCount + newDisplayCount, ds.size());
+ } else {
+ assertEquals("New virtual display must not be created",
+ originalDisplayCount, ds.size());
+ return Collections.emptyList();
+ }
+
+ // Find the newly added display(s).
+ final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds);
+ assertTrue("New virtual display must be created",
+ newDisplayCount == newDisplays.size());
+
+ return newDisplays;
+ }
+ }
+
+ /** Helper class to save, set, and restore overlay_display_devices preference. */
+ private static class OverlayDisplayDevicesSession extends SettingsSession<String> {
+ OverlayDisplayDevicesSession() {
+ super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
+ Settings.Global::getString,
+ Settings.Global::putString);
+ }
+ }
+
+ /** Wait for provided number of displays and report their configurations. */
+ List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
+ List<ActivityDisplay> ds = getDisplaysStates();
+
+ int retriesLeft = 5;
+ while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
+ log("***Waiting for the correct number of displays...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ ds = getDisplaysStates();
+ }
+
+ return ds;
+ }
+
+ private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
+ if (displays.size() != expectedDisplayCount) {
+ return false;
+ }
+ for (ActivityDisplay display : displays) {
+ if (display.mOverrideConfiguration.densityDpi == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Checks if the device supports multi-display. */
+ boolean supportsMultiDisplay() {
+ return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
new file mode 100644
index 0000000..d82c7c5
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.util.Size;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayTests
+ */
+public class ActivityManagerDisplayTests extends ActivityManagerDisplayTestBase {
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+
+ /**
+ * Tests that the global configuration is equal to the default display's override configuration.
+ */
+ @Test
+ public void testDefaultDisplayOverrideConfiguration() throws Exception {
+ final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+ final ActivityDisplay primaryDisplay = getDisplayState(reportedDisplays,
+ DEFAULT_DISPLAY_ID);
+ assertEquals("Primary display's configuration should be equal to global configuration.",
+ primaryDisplay.mOverrideConfiguration, primaryDisplay.mFullConfiguration);
+ assertEquals("Primary display's configuration should be equal to global configuration.",
+ primaryDisplay.mOverrideConfiguration, primaryDisplay.mMergedOverrideConfiguration);
+ }
+
+ /**
+ * Tests that secondary display has override configuration set.
+ */
+ @Test
+ public void testCreateVirtualDisplayWithCustomConfig() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Find the density of created display.
+ final int newDensityDpi = newDisplay.mFullConfiguration.densityDpi;
+ assertEquals(CUSTOM_DENSITY_DPI, newDensityDpi);
+ }
+ }
+
+ /**
+ * Tests that launch on secondary display is not permitted if device has the feature disabled.
+ * Activities requested to be launched on a secondary display in this case should land on the
+ * default display.
+ */
+ @Test
+ public void testMultiDisplayDisabled() throws Exception {
+ // Only check devices with the feature disabled.
+ assumeFalse(supportsMultiDisplay());
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ TEST_ACTIVITY_NAME);
+
+ // Check that activity is on the right display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed",
+ getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
+ assertEquals("Front stack must be on the default display", DEFAULT_DISPLAY_ID,
+ frontStack.mDisplayId);
+ mAmWmState.assertFocusedStack("Focus must be on the default display", frontStackId);
+ }
+ }
+
+ @Test
+ public void testCreateMultipleVirtualDisplays() throws Exception {
+ final List<ActivityDisplay> originalDs = getDisplaysStates();
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual displays
+ virtualDisplaySession.createDisplays(3);
+ getDisplayStateAfterChange(originalDs.size() + 3);
+ }
+ getDisplayStateAfterChange(originalDs.size());
+ }
+
+ /**
+ * Test that display overrides apply correctly and won't be affected by display changes.
+ * This sets overrides to display size and density, initiates a display changed event by locking
+ * and unlocking the phone and verifies that overrides are kept.
+ */
+ @Presubmit
+ @Test
+ public void testForceDisplayMetrics() throws Exception {
+ launchHomeActivity();
+
+ try (final DisplayMetricsSession displayMetricsSession = new DisplayMetricsSession()) {
+ // Read initial sizes.
+ final ReportedDisplayMetrics originalDisplayMetrics =
+ displayMetricsSession.getInitialDisplayMetrics();
+
+ // Apply new override values that don't match the physical metrics.
+ final Size overrideSize = new Size(
+ (int) (originalDisplayMetrics.physicalSize.getWidth() * 1.5),
+ (int) (originalDisplayMetrics.physicalSize.getHeight() * 1.5));
+ final Integer overrideDensity = (int) (originalDisplayMetrics.physicalDensity * 1.1);
+ displayMetricsSession.overrideDisplayMetrics(overrideSize, overrideDensity);
+
+ // Check if overrides applied correctly.
+ ReportedDisplayMetrics displayMetrics = displayMetricsSession.getDisplayMetrics();
+ assertEquals(overrideSize, displayMetrics.overrideSize);
+ assertEquals(overrideDensity, displayMetrics.overrideDensity);
+
+ // Lock and unlock device. This will cause a DISPLAY_CHANGED event to be triggered and
+ // might update the metrics.
+ sleepDevice();
+
+ wakeUpAndUnlockDevice();
+ mAmWmState.waitForHomeActivityVisible();
+
+ // Check if overrides are still applied.
+ displayMetrics = displayMetricsSession.getDisplayMetrics();
+ assertEquals(overrideSize, displayMetrics.overrideSize);
+ assertEquals(overrideDensity, displayMetrics.overrideDensity);
+ }
+ }
+
+ private static class DisplayMetricsSession implements AutoCloseable {
+
+ private final ReportedDisplayMetrics mInitialDisplayMetrics;
+
+ DisplayMetricsSession() throws Exception {
+ mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics();
+ }
+
+ ReportedDisplayMetrics getInitialDisplayMetrics() {
+ return mInitialDisplayMetrics;
+ }
+
+ ReportedDisplayMetrics getDisplayMetrics() throws Exception {
+ return ReportedDisplayMetrics.getDisplayMetrics();
+ }
+
+ void overrideDisplayMetrics(final Size size, final int density) {
+ mInitialDisplayMetrics.setDisplayMetrics(size, density);
+ }
+
+ @Override
+ public void close() throws Exception {
+ mInitialDisplayMetrics.restoreDisplayMetrics();
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
new file mode 100644
index 0000000..22d0934
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Rect;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerFreeformStackTests
+ */
+public class ActivityManagerFreeformStackTests extends ActivityManagerTestBase {
+
+ private static final String TEST_ACTIVITY = "TestActivity";
+ private static final int TEST_TASK_OFFSET = 20;
+ private static final int TEST_TASK_OFFSET_2 = 100;
+ private static final int TEST_TASK_SIZE_1 = 900;
+ private static final int TEST_TASK_SIZE_2 = TEST_TASK_SIZE_1 * 2;
+ private static final int TEST_TASK_SIZE_DP_1 = 220;
+ private static final int TEST_TASK_SIZE_DP_2 = TEST_TASK_SIZE_DP_1 * 2;
+
+ // NOTE: Launching the FreeformActivity will automatically launch the TestActivity
+ // with bounds (0, 0, 900, 900)
+ private static final String FREEFORM_ACTIVITY = "FreeformActivity";
+ private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
+ private static final String NO_RELAUNCH_ACTIVITY = "NoRelaunchActivity";
+
+ @Test
+ public void testFreeformWindowManagementSupport() throws Exception {
+
+ launchActivity(FREEFORM_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+ mAmWmState.computeState(FREEFORM_ACTIVITY, TEST_ACTIVITY);
+
+ if (!supportsFreeform()) {
+ mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ return;
+ }
+
+ mAmWmState.assertFrontStack("Freeform stack must be the front stack.",
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(FREEFORM_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+ mAmWmState.assertFocusedActivity(
+ TEST_ACTIVITY + " must be focused Activity", TEST_ACTIVITY);
+ assertEquals(new Rect(0, 0, TEST_TASK_SIZE_1, TEST_TASK_SIZE_1),
+ mAmWmState.getAmState().getTaskByActivityName(TEST_ACTIVITY).getBounds());
+ }
+
+ @Test
+ public void testNonResizeableActivityHasFullDisplayBounds() throws Exception {
+ launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY).build());
+
+ final ActivityTask task =
+ mAmWmState.getAmState().getTaskByActivityName(NON_RESIZEABLE_ACTIVITY);
+ final ActivityStack stack = mAmWmState.getAmState().getStackById(task.mStackId);
+
+ if (task.isFullscreen()) {
+ // If the task is on the fullscreen stack, then we know that it will have bounds that
+ // fill the entire display.
+ return;
+ }
+
+ // If the task is not on the fullscreen stack, then compare the task bounds to the display
+ // bounds.
+ assertEquals(mAmWmState.getWmState().getDisplay(stack.mDisplayId).getDisplayRect(),
+ task.getBounds());
+ }
+
+ @Test
+ public void testActivityLifeCycleOnResizeFreeformTask() throws Exception {
+ launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FREEFORM);
+ launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
+ new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
+
+ if (!supportsFreeform()) {
+ mAmWmState.assertDoesNotContainStack("Must not contain freeform stack.",
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
+ return;
+ }
+
+ final int displayId = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_FREEFORM).mDisplayId;
+ final int densityDpi =
+ mAmWmState.getWmState().getDisplay(displayId).getDpi();
+ final int testTaskSize1 =
+ ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_1, densityDpi);
+ final int testTaskSize2 =
+ ActivityAndWindowManagersState.dpToPx(TEST_TASK_SIZE_DP_2, densityDpi);
+
+ resizeActivityTask(TEST_ACTIVITY,
+ TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize1, testTaskSize2);
+ resizeActivityTask(NO_RELAUNCH_ACTIVITY,
+ TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize1, testTaskSize2);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
+ new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
+
+ final String logSeparator = clearLogcat();
+ resizeActivityTask(TEST_ACTIVITY,
+ TEST_TASK_OFFSET, TEST_TASK_OFFSET, testTaskSize2, testTaskSize1);
+ resizeActivityTask(NO_RELAUNCH_ACTIVITY,
+ TEST_TASK_OFFSET_2, TEST_TASK_OFFSET_2, testTaskSize2, testTaskSize1);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
+ new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
+
+ assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
+ assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
new file mode 100644
index 0000000..cb9be66
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.ActivityAndWindowManagersState.dpToPx;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.graphics.Rect;
+import android.server.am.WindowManagerState.Display;
+import android.server.am.WindowManagerState.WindowState;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerManifestLayoutTests
+ */
+public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
+
+ // Test parameters
+ private static final int DEFAULT_WIDTH_DP = 240;
+ private static final int DEFAULT_HEIGHT_DP = 160;
+ private static final float DEFAULT_WIDTH_FRACTION = 0.25f;
+ private static final float DEFAULT_HEIGHT_FRACTION = 0.35f;
+ private static final int MIN_WIDTH_DP = 100;
+ private static final int MIN_HEIGHT_DP = 80;
+
+ private static final int GRAVITY_VER_CENTER = 0x01;
+ private static final int GRAVITY_VER_TOP = 0x02;
+ private static final int GRAVITY_VER_BOTTOM = 0x04;
+ private static final int GRAVITY_HOR_CENTER = 0x10;
+ private static final int GRAVITY_HOR_LEFT = 0x20;
+ private static final int GRAVITY_HOR_RIGHT = 0x40;
+
+ private List<WindowState> mTempWindowList = new ArrayList();
+ private Display mDisplay;
+ private WindowState mWindowState;
+
+ @Test
+ public void testGravityAndDefaultSizeTopLeft() throws Exception {
+ testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/);
+ }
+
+ @Test
+ public void testGravityAndDefaultSizeTopRight() throws Exception {
+ testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/);
+ }
+
+ @Test
+ public void testGravityAndDefaultSizeBottomLeft() throws Exception {
+ testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/);
+ }
+
+ @Test
+ public void testGravityAndDefaultSizeBottomRight() throws Exception {
+ testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/);
+ }
+
+ @Test
+ public void testMinimalSizeFreeform() throws Exception {
+ assumeTrue("Skipping test: no freeform support", supportsFreeform());
+
+ testMinimalSize(WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testMinimalSizeDocked() throws Exception {
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+ testMinimalSize(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ }
+
+ private void testMinimalSize(int windowingMode) throws Exception {
+ final String activityName = "BottomRightLayoutActivity";
+
+ // Issue command to resize to <0,0,1,1>. We expect the size to be floored at
+ // MIN_WIDTH_DPxMIN_HEIGHT_DP.
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ launchActivity(activityName, WINDOWING_MODE_FREEFORM);
+ resizeActivityTask(activityName, 0, 0, 1, 1);
+ } else { // stackId == DOCKED_STACK_ID
+ launchActivityInSplitScreenWithRecents(activityName);
+ resizeDockedStack(1, 1, 1, 1);
+ }
+ getDisplayAndWindowState(activityName, false);
+
+ final int minWidth = dpToPx(MIN_WIDTH_DP, mDisplay.getDpi());
+ final int minHeight = dpToPx(MIN_HEIGHT_DP, mDisplay.getDpi());
+ final Rect containingRect = mWindowState.getContainingFrame();
+
+ Assert.assertEquals("Min width is incorrect", minWidth, containingRect.width());
+ Assert.assertEquals("Min height is incorrect", minHeight, containingRect.height());
+ }
+
+ private void testLayout(
+ int vGravity, int hGravity, boolean fraction) throws Exception {
+ assumeTrue("Skipping test: no freeform support", supportsFreeform());
+
+ final String activityName = (vGravity == GRAVITY_VER_TOP ? "Top" : "Bottom")
+ + (hGravity == GRAVITY_HOR_LEFT ? "Left" : "Right") + "LayoutActivity";
+
+ // Launch in freeform stack
+ launchActivity(activityName, WINDOWING_MODE_FREEFORM);
+
+ getDisplayAndWindowState(activityName, true);
+
+ final Rect containingRect = mWindowState.getContainingFrame();
+ final Rect appRect = mDisplay.getAppRect();
+ final int expectedWidthPx, expectedHeightPx;
+ // Evaluate the expected window size in px. If we're using fraction dimensions,
+ // calculate the size based on the app rect size. Otherwise, convert the expected
+ // size in dp to px.
+ if (fraction) {
+ expectedWidthPx = (int) (appRect.width() * DEFAULT_WIDTH_FRACTION);
+ expectedHeightPx = (int) (appRect.height() * DEFAULT_HEIGHT_FRACTION);
+ } else {
+ final int densityDpi = mDisplay.getDpi();
+ expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
+ expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
+ }
+
+ verifyFrameSizeAndPosition(
+ vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
+ }
+
+ private void getDisplayAndWindowState(String activityName, boolean checkFocus)
+ throws Exception {
+ final String windowName = getWindowName(activityName);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+
+ if (checkFocus) {
+ mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+ } else {
+ mAmWmState.assertVisibility(activityName, true);
+ }
+
+ mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mTempWindowList);
+
+ Assert.assertEquals("Should have exactly one window state for the activity.",
+ 1, mTempWindowList.size());
+
+ mWindowState = mTempWindowList.get(0);
+ Assert.assertNotNull("Should have a valid window", mWindowState);
+
+ mDisplay = mAmWmState.getWmState().getDisplay(mWindowState.getDisplayId());
+ Assert.assertNotNull("Should be on a display", mDisplay);
+ }
+
+ private void verifyFrameSizeAndPosition(
+ int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
+ Rect containingFrame, Rect parentFrame) {
+ Assert.assertEquals("Width is incorrect", expectedWidthPx, containingFrame.width());
+ Assert.assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height());
+
+ if (vGravity == GRAVITY_VER_TOP) {
+ Assert.assertEquals("Should be on the top", parentFrame.top, containingFrame.top);
+ } else if (vGravity == GRAVITY_VER_BOTTOM) {
+ Assert.assertEquals("Should be on the bottom",
+ parentFrame.bottom, containingFrame.bottom);
+ }
+
+ if (hGravity == GRAVITY_HOR_LEFT) {
+ Assert.assertEquals("Should be on the left", parentFrame.left, containingFrame.left);
+ } else if (hGravity == GRAVITY_HOR_RIGHT){
+ Assert.assertEquals("Should be on the right",
+ parentFrame.right, containingFrame.right);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
new file mode 100644
index 0000000..b79d4ce
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -0,0 +1,1800 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics
+ .getDisplayMetrics;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+import android.server.am.displayservice.DisplayHelper;
+import android.support.annotation.Nullable;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerMultiDisplayTests
+ */
+public class ActivityManagerMultiDisplayTests extends ActivityManagerDisplayTestBase {
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
+ private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
+ private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
+ private static final String SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME = "ShowWhenLockedAttrActivity";
+ private static final String SECOND_PACKAGE = "android.server.am.second";
+ private static final String THIRD_PACKAGE = "android.server.am.third";
+ private static final ComponentName SECOND_ACTIVITY = ComponentName.createRelative(
+ SECOND_PACKAGE, ".SecondActivity");
+ private static final ComponentName SECOND_NO_EMBEDDING_ACTIVITY = ComponentName.createRelative(
+ SECOND_PACKAGE, ".SecondActivityNoEmbedding");
+ private static final ComponentName LAUNCH_BROADCAST_RECEIVER = ComponentName.createRelative(
+ SECOND_PACKAGE, ".LaunchBroadcastReceiver");
+ /** See AndroidManifest.xml of appSecondUid. */
+ private static final String LAUNCH_BROADCAST_ACTION =
+ SECOND_PACKAGE + ".LAUNCH_BROADCAST_ACTION";
+ private static final ComponentName THIRD_ACTIVITY = ComponentName.createRelative(
+ THIRD_PACKAGE, ".ThirdActivity");
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(supportsMultiDisplay());
+ }
+
+ /**
+ * Tests launching an activity on virtual display.
+ */
+ @Presubmit
+ @Test
+ public void testLaunchActivityOnSecondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ final String logSeparator = clearLogcat();
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ TEST_ACTIVITY_NAME);
+
+ // Check that activity is on the right display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be on the secondary display and resumed",
+ getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // Check that activity config corresponds to display config.
+ final ReportedSizes reportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY_NAME,
+ logSeparator);
+ assertEquals("Activity launched on secondary display must have proper configuration",
+ CUSTOM_DENSITY_DPI, reportedSizes.densityDpi);
+ }
+ }
+
+ /**
+ * Tests launching a non-resizeable activity on virtual display. It should land on the
+ * default display.
+ */
+ @Test
+ public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY_NAME));
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ NON_RESIZEABLE_ACTIVITY_NAME);
+
+ // Check that activity is on the right display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be on the primary display and resumed",
+ getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
+ }
+ }
+
+ /**
+ * Tests launching a non-resizeable activity on virtual display while split-screen is active
+ * on the primary display. It should land on the primary display and dismiss docked stack.
+ */
+ @Test
+ public void testLaunchNonResizeableActivityWithSplitScreen() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Start launching activity.
+ launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.setLaunchInSplitScreen(true)
+ .createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(NON_RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY_NAME));
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ NON_RESIZEABLE_ACTIVITY_NAME);
+
+ // Check that activity is on the right display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be on the primary display and resumed",
+ getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
+ mAmWmState.assertDoesNotContainStack("Must not contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+ }
+
+ /**
+ * Tests moving a non-resizeable activity to a virtual display. It should stay on the default
+ * display with no action performed.
+ */
+ @Test
+ public void testMoveNonResizeableActivityToSecondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ // Launch a non-resizeable activity on a primary display.
+ launchActivityInNewTask(NON_RESIZEABLE_ACTIVITY_NAME);
+ // Launch a resizeable activity on new secondary display to create a new stack there.
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ final int externalFrontStackId = mAmWmState.getAmState()
+ .getFrontStackId(newDisplay.mId);
+
+ // Try to move the non-resizeable activity to new secondary display.
+ moveActivityToStack(NON_RESIZEABLE_ACTIVITY_NAME, externalFrontStackId);
+ mAmWmState.computeState(new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY_NAME));
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ RESIZEABLE_ACTIVITY_NAME);
+
+ // Check that activity is in the same stack
+ final int defaultFrontStackId = mAmWmState.getAmState().getFrontStackId(
+ DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack defaultFrontStack =
+ mAmWmState.getAmState().getStackById(defaultFrontStackId);
+ assertEquals("Launched activity must be on the primary display and resumed",
+ getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
+ defaultFrontStack.getTopTask().mRealActivity);
+ mAmWmState.assertFocusedStack("Focus must remain on the secondary display",
+ externalFrontStackId);
+ }
+ }
+
+ /**
+ * Tests launching a non-resizeable activity on virtual display from activity there. It should
+ * land on the secondary display based on the resizeability of the root activity of the task.
+ */
+ @Test
+ public void testLaunchNonResizeableActivityFromSecondaryDisplaySameTask() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new simulated display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ .createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ BROADCAST_RECEIVER_ACTIVITY);
+
+ // Check that launching activity is on the secondary display.
+ int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be on the secondary display and resumed",
+ getActivityComponentName(BROADCAST_RECEIVER_ACTIVITY),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
+
+ // Launch non-resizeable activity from secondary display.
+ executeShellCommand("am broadcast -a trigger_broadcast --ez launch_activity true "
+ + "--ez new_task true --es target_activity " + NON_RESIZEABLE_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY_NAME));
+
+ // Check that non-resizeable activity is on the secondary display, because of the
+ // resizeable root of the task.
+ frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be on the primary display and resumed",
+ getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on the primary display", frontStackId);
+ }
+ }
+
+ /**
+ * Tests launching a non-resizeable activity on virtual display from activity there. It should
+ * land on some different suitable display (usually - on the default one).
+ */
+ @Test
+ public void testLaunchNonResizeableActivityFromSecondaryDisplayNewTask() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ LAUNCHING_ACTIVITY);
+
+ // Check that launching activity is on the secondary display.
+ int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be on the secondary display and resumed",
+ getActivityComponentName(LAUNCHING_ACTIVITY),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on the secondary display", frontStackId);
+
+ // Launch non-resizeable activity from secondary display.
+ getLaunchActivityBuilder().setTargetActivityName(NON_RESIZEABLE_ACTIVITY_NAME)
+ .setNewTask(true).setMultipleTask(true).execute();
+
+ // Check that non-resizeable activity is on the primary display.
+ frontStackId = mAmWmState.getAmState().getFocusedStackId();
+ frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertFalse("Launched activity must be on a different display",
+ newDisplay.mId == frontStack.mDisplayId);
+ assertEquals("Launched activity must be resumed",
+ getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on a just launched activity",
+ frontStackId);
+ }
+ }
+
+ /**
+ * Tests launching an activity on a virtual display without special permission must not be
+ * allowed.
+ */
+ @Test
+ public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ final String logSeparator = clearLogcat();
+
+ // Try to launch an activity and check it security exception was triggered.
+ final String broadcastTarget = "-a " + LAUNCH_BROADCAST_ACTION
+ + " -p " + LAUNCH_BROADCAST_RECEIVER.getPackageName();
+ final String includeStoppedPackagesFlag = " -f 0x00000020";
+ executeShellCommand("am broadcast " + broadcastTarget
+ + " --ez launch_activity true --es target_activity " + TEST_ACTIVITY_NAME
+ + " --es package_name " + componentName
+ + " --ei display_id " + newDisplay.mId
+ + includeStoppedPackagesFlag);
+
+ assertSecurityException("LaunchBroadcastReceiver", logSeparator);
+
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+ assertFalse("Restricted activity must not be launched",
+ mAmWmState.getAmState().containsActivity(TEST_ACTIVITY_NAME));
+ }
+ }
+
+ /**
+ * Tests launching an activity on a virtual display without special permission must be allowed
+ * for activities with same UID.
+ */
+ @Test
+ public void testLaunchWithoutPermissionOnVirtualDisplayByOwner() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Try to launch an activity and check it security exception was triggered.
+ final String broadcastTarget = "-a " + componentName + ".LAUNCH_BROADCAST_ACTION"
+ + " -p " + componentName;
+ executeShellCommand("am broadcast " + broadcastTarget
+ + " --ez launch_activity true --es target_activity " + TEST_ACTIVITY_NAME
+ + " --es package_name " + componentName
+ + " --ei display_id " + newDisplay.mId);
+
+ mAmWmState.waitForValidState(TEST_ACTIVITY_NAME);
+
+ final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack =
+ mAmWmState.getAmState().getStackById(externalFocusedStackId);
+ assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+ focusedStack.mDisplayId);
+
+ mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
+ TEST_ACTIVITY_NAME);
+ assertEquals("Activity launched by owner must be on external display",
+ externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+ }
+ }
+
+ /**
+ * Tests launching an activity on virtual display and then launching another activity via shell
+ * command and without specifying the display id - the second activity must appear on the
+ * primary display.
+ */
+ @Presubmit
+ @Test
+ public void testConsequentLaunchActivity() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ TEST_ACTIVITY_NAME);
+
+ // Launch second activity without specifying display.
+ launchActivity(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ LAUNCHING_ACTIVITY);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack frontStack
+ = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ getActivityComponentName(LAUNCHING_ACTIVITY), frontStack.mResumedActivity);
+ assertEquals("Front stack must be on primary display",
+ DEFAULT_DISPLAY_ID, frontStack.mDisplayId);
+ }
+ }
+
+ /**
+ * Tests launching an activity on simulated display and then launching another activity from the
+ * first one - it must appear on the secondary display, because it was launched from there.
+ */
+ @FlakyTest(bugId = 71564456)
+ @Presubmit
+ @Test
+ public void testConsequentLaunchActivityFromSecondaryDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new simulated display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ .createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be resumed",
+ LAUNCHING_ACTIVITY);
+
+ // Launch second activity from app on secondary display without specifying display id.
+ getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+
+ // Check that activity is launched in focused stack on external display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ TEST_ACTIVITY_NAME);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack frontStack
+ = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
+ }
+ }
+
+ /**
+ * Tests launching an activity on virtual display and then launching another activity from the
+ * first one - it must appear on the secondary display, because it was launched from there.
+ */
+ @Test
+ public void testConsequentLaunchActivityFromVirtualDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be resumed",
+ LAUNCHING_ACTIVITY);
+
+ // Launch second activity from app on secondary display without specifying display id.
+ getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
+ mAmWmState.computeState(TEST_ACTIVITY_NAME);
+
+ // Check that activity is launched in focused stack on external display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ TEST_ACTIVITY_NAME);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack frontStack = mAmWmState.getAmState()
+ .getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
+ }
+ }
+
+ /**
+ * Tests launching an activity on virtual display and then launching another activity from the
+ * first one with specifying the target display - it must appear on the secondary display.
+ */
+ @Test
+ public void testConsequentLaunchActivityFromVirtualDisplayToTargetDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be resumed",
+ LAUNCHING_ACTIVITY);
+
+ // Launch second activity from app on secondary display specifying same display id.
+ getLaunchActivityBuilder()
+ .setTargetActivity(SECOND_ACTIVITY)
+ .setDisplayId(newDisplay.mId)
+ .execute();
+ mAmWmState.computeState(TEST_ACTIVITY_NAME);
+
+ // Check that activity is launched in focused stack on external display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused", SECOND_ACTIVITY);
+ int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ SECOND_ACTIVITY.flattenToShortString(), frontStack.mResumedActivity);
+
+ // Launch other activity with different uid and check if it has launched successfully.
+ getLaunchActivityBuilder()
+ .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
+ .setDisplayId(newDisplay.mId)
+ .setTargetActivity(THIRD_ACTIVITY)
+ .execute();
+ mAmWmState.waitForValidState(new WaitForValidActivityState(THIRD_ACTIVITY));
+
+ // Check that activity is launched in focused stack on external display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused", THIRD_ACTIVITY);
+ frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ THIRD_ACTIVITY.flattenToShortString(), frontStack.mResumedActivity);
+ }
+ }
+
+ /**
+ * Tests launching an activity on virtual display and then launching another activity that
+ * doesn't allow embedding - it should fail with security exception.
+ */
+ @Test
+ public void testConsequentLaunchActivityFromVirtualDisplayNoEmbedding() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(LAUNCHING_ACTIVITY);
+
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be resumed",
+ LAUNCHING_ACTIVITY);
+
+ final String logSeparator = clearLogcat();
+
+ // Launch second activity from app on secondary display specifying same display id.
+ getLaunchActivityBuilder()
+ .setTargetActivity(SECOND_NO_EMBEDDING_ACTIVITY)
+ .setDisplayId(newDisplay.mId)
+ .execute();
+
+ assertSecurityException("ActivityLauncher", logSeparator);
+ }
+ }
+
+ /**
+ * Tests launching an activity to secondary display from activity on primary display.
+ */
+ @Test
+ public void testLaunchActivityFromAppToSecondaryDisplay() throws Exception {
+ // Start launching activity.
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new simulated display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ .createDisplay();
+
+ // Launch activity on secondary display from the app on primary display.
+ getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY_NAME)
+ .setDisplayId(newDisplay.mId).execute();
+
+ // Check that activity is launched on external display.
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ TEST_ACTIVITY_NAME);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack frontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ getActivityComponentName(TEST_ACTIVITY_NAME), frontStack.mResumedActivity);
+ }
+ }
+
+ /**
+ * Tests launching activities on secondary and then on primary display to see if the stack
+ * visibility is not affected.
+ */
+ @Presubmit
+ @Test
+ public void testLaunchActivitiesAffectsVisibility() throws Exception {
+ // Start launching activity.
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ // Launch activity on primary display and check if it doesn't affect activity on
+ // secondary display.
+ getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
+ mAmWmState.waitForValidState(RESIZEABLE_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
+ }
+ }
+
+ /**
+ * Test that move-task works when moving between displays.
+ */
+ @Presubmit
+ @Test
+ public void testMoveTaskBetweenDisplays() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+ defaultDisplayStackId);
+ assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+ focusedStack.mDisplayId);
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ TEST_ACTIVITY_NAME);
+ int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Focused stack must be on secondary display",
+ newDisplay.mId, focusedStack.mDisplayId);
+
+ // Move activity from secondary display to primary.
+ moveActivityToStack(TEST_ACTIVITY_NAME, defaultDisplayStackId);
+ mAmWmState.waitForFocusedStack(defaultDisplayStackId);
+ mAmWmState.assertFocusedActivity("Focus must be on moved activity", TEST_ACTIVITY_NAME);
+ focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ focusedStack = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Focus must return to primary display", DEFAULT_DISPLAY_ID,
+ focusedStack.mDisplayId);
+ }
+ }
+
+ /**
+ * Tests launching activities on secondary display and then removing it to see if stack focus
+ * is moved correctly.
+ * This version launches virtual display creator to fullscreen stack in split-screen.
+ */
+ @FlakyTest(bugId = 69573940)
+ @Presubmit
+ @Test
+ public void testStackFocusSwitchOnDisplayRemoved() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Start launching activity into docked stack.
+ launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+ tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ }
+
+ /**
+ * Tests launching activities on secondary display and then removing it to see if stack focus
+ * is moved correctly.
+ * This version launches virtual display creator to docked stack in split-screen.
+ */
+ @Test
+ public void testStackFocusSwitchOnDisplayRemoved2() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Setup split-screen.
+ launchActivitiesInSplitScreen(RESIZEABLE_ACTIVITY_NAME, LAUNCHING_ACTIVITY);
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+ tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ }
+
+ /**
+ * Tests launching activities on secondary display and then removing it to see if stack focus
+ * is moved correctly.
+ * This version works without split-screen.
+ */
+ @Test
+ public void testStackFocusSwitchOnDisplayRemoved3() throws Exception {
+ // Start an activity on default display to determine default stack.
+ launchActivity(BROADCAST_RECEIVER_ACTIVITY);
+ final int focusedStackWindowingMode = mAmWmState.getAmState().getFrontStackWindowingMode(
+ DEFAULT_DISPLAY_ID);
+ // Finish probing activity.
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+ tryCreatingAndRemovingDisplayWithActivity(false /* splitScreen */,
+ focusedStackWindowingMode);
+ }
+
+ /**
+ * Create a virtual display, launch a test activity there, destroy the display and check if test
+ * activity is moved to a stack on the default display.
+ */
+ private void tryCreatingAndRemovingDisplayWithActivity(boolean splitScreen, int windowingMode)
+ throws Exception {
+ String logSeparator;
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession
+ .setPublicDisplay(true)
+ .setLaunchInSplitScreen(splitScreen)
+ .createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ if (splitScreen) {
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+ }
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ TEST_ACTIVITY_NAME);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // Destroy virtual display.
+ logSeparator = clearLogcat();
+ }
+
+ assertActivityLifecycle(TEST_ACTIVITY_NAME, false /* relaunched */, logSeparator);
+ mAmWmState.waitForValidState(TEST_ACTIVITY_NAME, windowingMode, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertSanity();
+ mAmWmState.assertValidBounds(true /* compareTaskAndStackBounds */);
+
+ // Check if the focus is switched back to primary display.
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+ mAmWmState.assertFocusedStack(
+ "Default stack on primary display must be focused after display removed",
+ windowingMode, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedActivity(
+ "Focus must be switched back to activity on primary display",
+ TEST_ACTIVITY_NAME);
+ }
+
+ /**
+ * Tests launching activities on secondary display and then removing it to see if stack focus
+ * is moved correctly.
+ */
+ @Test
+ public void testStackFocusSwitchOnStackEmptied() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ final int focusedStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ BROADCAST_RECEIVER_ACTIVITY);
+
+ // Lock the device, so that activity containers will be detached.
+ sleepDevice();
+
+ // Finish activity on secondary display.
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+ // Unlock and check if the focus is switched back to primary display.
+ wakeUpAndUnlockDevice();
+ mAmWmState.waitForFocusedStack(focusedStackId);
+ mAmWmState.waitForValidState(VIRTUAL_DISPLAY_ACTIVITY);
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ }
+ }
+
+ /**
+ * Tests that input events on the primary display take focus from the virtual display.
+ */
+ @Test
+ public void testStackFocusSwitchOnTouchEvent() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ mAmWmState.computeState(new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+ mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+ VIRTUAL_DISPLAY_ACTIVITY);
+
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+ mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+ mAmWmState.assertFocusedActivity(
+ "Activity launched on secondary display must be focused",
+ TEST_ACTIVITY_NAME);
+
+ final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+ final int width = displayMetrics.getSize().getWidth();
+ final int height = displayMetrics.getSize().getHeight();
+ executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
+
+ mAmWmState.computeState(new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+ mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ }
+ }
+
+ /** Test that shell is allowed to launch on secondary displays. */
+ @Test
+ public void testPermissionLaunchFromShell() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+ defaultDisplayFocusedStackId);
+ assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+ focusedStack.mDisplayId);
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ TEST_ACTIVITY_NAME);
+ final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
+ assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+ focusedStack.mDisplayId);
+
+ // Launch other activity with different uid and check it is launched on dynamic stack on
+ // secondary display.
+ final String startCmd = "am start -n " + SECOND_ACTIVITY.flattenToShortString()
+ + " --display " + newDisplay.mId;
+ executeShellCommand(startCmd);
+
+ mAmWmState.waitForValidState(new WaitForValidActivityState(SECOND_ACTIVITY));
+ mAmWmState.assertFocusedActivity(
+ "Focus must be on newly launched app", SECOND_ACTIVITY);
+ assertEquals("Activity launched by system must be on external display",
+ externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+ }
+ }
+
+ /** Test that launching from app that is on external display is allowed. */
+ @Test
+ public void testPermissionLaunchFromAppOnSecondary() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new simulated display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ .createDisplay();
+
+ // Launch activity with different uid on secondary display.
+ final String startCmd = "am start -n " + SECOND_ACTIVITY.flattenToShortString()
+ + " --display " + newDisplay.mId;
+ executeShellCommand(startCmd);
+
+ mAmWmState.waitForValidState(new WaitForValidActivityState(SECOND_ACTIVITY));
+ mAmWmState.assertFocusedActivity(
+ "Focus must be on newly launched app", SECOND_ACTIVITY);
+ final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(externalFocusedStackId);
+ assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+ focusedStack.mDisplayId);
+
+ // Launch another activity with third different uid from app on secondary display and
+ // check it is launched on secondary display.
+ final String targetActivity =
+ " --es target_activity " + THIRD_ACTIVITY.getShortClassName()
+ + " --es package_name " + THIRD_ACTIVITY.getPackageName()
+ + " --ei display_id " + newDisplay.mId;
+ final String includeStoppedPackagesFlag = " -f 0x00000020";
+ executeShellCommand("am broadcast -a " + LAUNCH_BROADCAST_ACTION
+ + " -p " + LAUNCH_BROADCAST_RECEIVER.getPackageName()
+ + targetActivity + includeStoppedPackagesFlag);
+
+ mAmWmState.waitForValidState(new WaitForValidActivityState(THIRD_ACTIVITY));
+ mAmWmState.assertFocusedActivity("Focus must be on newly launched app", THIRD_ACTIVITY);
+ assertEquals("Activity launched by app on secondary display must be on that display",
+ externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+ }
+ }
+
+ /** Tests that an activity can launch an activity from a different UID into its own task. */
+ @Test
+ public void testPermissionLaunchMultiUidTask() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ .createDisplay();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+
+ // Check that the first activity is launched onto the secondary display
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ ActivityManagerState.ActivityStack frontStack = mAmWmState.getAmState().getStackById(
+ frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(LAUNCHING_ACTIVITY),
+ frontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // Launch an activity from a different UID into the first activity's task
+ getLaunchActivityBuilder().setTargetActivity(SECOND_ACTIVITY).execute();
+
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+ frontStack = mAmWmState.getAmState().getStackById(frontStackId);
+ mAmWmState.assertFocusedActivity(
+ "Focus must be on newly launched app", SECOND_ACTIVITY);
+ assertEquals("Secondary display must contain 1 task", 1, frontStack.getTasks().size());
+ }
+ }
+
+ /**
+ * Test that launching from display owner is allowed even when the the display owner
+ * doesn't have anything on the display.
+ */
+ @Test
+ public void testPermissionLaunchFromOwner() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(defaultDisplayFocusedStackId);
+ assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+ focusedStack.mDisplayId);
+
+ // Launch other activity with different uid on secondary display.
+ final String startCmd = "am start -n " + SECOND_ACTIVITY.flattenToShortString()
+ + " --display " + newDisplay.mId;
+ executeShellCommand(startCmd);
+
+ mAmWmState.waitForValidState(new WaitForValidActivityState(SECOND_ACTIVITY));
+ mAmWmState.assertFocusedActivity(
+ "Focus must be on newly launched app", SECOND_ACTIVITY);
+ final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
+ assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+ focusedStack.mDisplayId);
+
+ // Check that owner uid can launch its own activity on secondary display.
+ final String broadcastAction = componentName + ".LAUNCH_BROADCAST_ACTION";
+ executeShellCommand("am broadcast -a " + broadcastAction + " -p " + componentName
+ + " --ez launch_activity true --ez new_task true --ez multiple_task true"
+ + " --ei display_id " + newDisplay.mId);
+
+ mAmWmState.waitForValidState(TEST_ACTIVITY_NAME);
+ mAmWmState.assertFocusedActivity("Focus must be on newly launched app",
+ TEST_ACTIVITY_NAME);
+ assertEquals("Activity launched by owner must be on external display",
+ externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+ }
+ }
+
+ /**
+ * Test that launching from app that is not present on external display and doesn't own it to
+ * that external display is not allowed.
+ */
+ @Test
+ public void testPermissionLaunchFromDifferentApp() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ mAmWmState.assertFocusedActivity("Virtual display activity must be focused",
+ VIRTUAL_DISPLAY_ACTIVITY);
+ final int defaultDisplayFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ ActivityManagerState.ActivityStack focusedStack = mAmWmState.getAmState().getStackById(
+ defaultDisplayFocusedStackId);
+ assertEquals("Focus must remain on primary display", DEFAULT_DISPLAY_ID,
+ focusedStack.mDisplayId);
+
+ // Launch activity on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ TEST_ACTIVITY_NAME);
+ final int externalFocusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ focusedStack = mAmWmState.getAmState().getStackById(externalFocusedStackId);
+ assertEquals("Focused stack must be on secondary display", newDisplay.mId,
+ focusedStack.mDisplayId);
+
+ final String logSeparator = clearLogcat();
+
+ // Launch other activity with different uid and check security exception is triggered.
+ final String includeStoppedPackagesFlag = " -f 0x00000020";
+ executeShellCommand("am broadcast -a " + LAUNCH_BROADCAST_ACTION
+ + " -p " + LAUNCH_BROADCAST_RECEIVER.getPackageName()
+ + " --ei display_id " + newDisplay.mId
+ + includeStoppedPackagesFlag);
+
+ assertSecurityException("LaunchBroadcastReceiver", logSeparator);
+
+ mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, componentName,
+ new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+ mAmWmState.assertFocusedActivity("Focus must be on first activity", TEST_ACTIVITY_NAME);
+ assertEquals("Focused stack must be on secondary display's stack",
+ externalFocusedStackId, mAmWmState.getAmState().getFocusedStackId());
+ }
+ }
+
+ private void assertSecurityException(String component, String logSeparator) throws Exception {
+ int tries = 0;
+ boolean match = false;
+ final Pattern pattern = Pattern.compile(".*SecurityException launching activity.*");
+ while (tries < 5 && !match) {
+ String[] logs = getDeviceLogsForComponent(component, logSeparator);
+ for (String line : logs) {
+ Matcher m = pattern.matcher(line);
+ if (m.matches()) {
+ match = true;
+ break;
+ }
+ }
+ tries++;
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ assertTrue("Expected exception not found", match);
+ }
+
+ /**
+ * Test that only private virtual display can show content with insecure keyguard.
+ */
+ @Test
+ public void testFlagShowWithInsecureKeyguardOnPublicVirtualDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Try to create new show-with-insecure-keyguard public virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession
+ .setPublicDisplay(true)
+ .setCanShowWithInsecureKeyguard(true)
+ .setMustBeCreated(false)
+ .createDisplay();
+
+ // Check that the display is not created.
+ assertNull(newDisplay);
+ }
+ }
+
+ /**
+ * Test that all activities that were on the private display are destroyed on display removal.
+ */
+ @FlakyTest(bugId = 63404575)
+ @Presubmit
+ @Test
+ public void testContentDestroyOnDisplayRemoved() throws Exception {
+ String logSeparator;
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new private virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ // Launch activities on new secondary display.
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ TEST_ACTIVITY_NAME);
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ RESIZEABLE_ACTIVITY_NAME);
+
+ // Destroy the display and check if activities are removed from system.
+ logSeparator = clearLogcat();
+ }
+
+ final String activityName1 = ActivityManagerTestBase.getActivityComponentName(
+ TEST_ACTIVITY_NAME);
+ final String activityName2 = ActivityManagerTestBase.getActivityComponentName(
+ RESIZEABLE_ACTIVITY_NAME);
+ final String windowName1 = ActivityManagerTestBase.getWindowName(TEST_ACTIVITY_NAME);
+ final String windowName2 = ActivityManagerTestBase.getWindowName(RESIZEABLE_ACTIVITY_NAME);
+ mAmWmState.waitForWithAmState(
+ (state) -> !state.containsActivity(activityName1)
+ && !state.containsActivity(activityName2),
+ "Waiting for activity to be removed");
+ mAmWmState.waitForWithWmState(
+ (state) -> !state.containsWindow(windowName1)
+ && !state.containsWindow(windowName2),
+ "Waiting for activity window to be gone");
+
+ // Check AM state.
+ assertFalse("Activity from removed display must be destroyed",
+ mAmWmState.getAmState().containsActivity(activityName1));
+ assertFalse("Activity from removed display must be destroyed",
+ mAmWmState.getAmState().containsActivity(activityName2));
+ // Check WM state.
+ assertFalse("Activity windows from removed display must be destroyed",
+ mAmWmState.getWmState().containsWindow(windowName1));
+ assertFalse("Activity windows from removed display must be destroyed",
+ mAmWmState.getWmState().containsWindow(windowName2));
+ // Check activity logs.
+ assertActivityDestroyed(TEST_ACTIVITY_NAME, logSeparator);
+ assertActivityDestroyed(RESIZEABLE_ACTIVITY_NAME, logSeparator);
+ }
+
+ /**
+ * Test that the update of display metrics updates all its content.
+ */
+ @Presubmit
+ @Test
+ public void testDisplayResize() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ // Launch a resizeable activity on new secondary display.
+ final String initialLogSeparator = clearLogcat();
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ RESIZEABLE_ACTIVITY_NAME);
+
+ // Grab reported sizes and compute new with slight size change.
+ final ReportedSizes initialSize = getLastReportedSizesForActivity(
+ RESIZEABLE_ACTIVITY_NAME,
+ initialLogSeparator);
+
+ // Resize the docked stack, so that activity with virtual display will also be resized.
+ final String logSeparator = clearLogcat();
+ executeShellCommand(getResizeVirtualDisplayCommand());
+
+ mAmWmState.waitForWithAmState(amState -> {
+ try {
+ return readConfigChangeNumber(RESIZEABLE_ACTIVITY_NAME, logSeparator) == 1
+ && amState.hasActivityState(RESIZEABLE_ACTIVITY_NAME, STATE_RESUMED);
+ } catch (Exception e) {
+ logE("Error waiting for valid state: " + e.getMessage());
+ return false;
+ }
+ }, "Wait for the configuration change to happen and for activity to be resumed.");
+
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState(RESIZEABLE_ACTIVITY_NAME),
+ new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true);
+
+ // Check if activity in virtual display was resized properly.
+ assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY_NAME, 0 /* numRelaunch */,
+ 1 /* numConfigChange */, logSeparator);
+
+ final ReportedSizes updatedSize = getLastReportedSizesForActivity(
+ RESIZEABLE_ACTIVITY_NAME,
+ logSeparator);
+ assertTrue(updatedSize.widthDp <= initialSize.widthDp);
+ assertTrue(updatedSize.heightDp <= initialSize.heightDp);
+ assertTrue(updatedSize.displayWidth == initialSize.displayWidth / 2);
+ assertTrue(updatedSize.displayHeight == initialSize.displayHeight / 2);
+ }
+ }
+
+ /** Read the number of configuration changes sent to activity from logs. */
+ private int readConfigChangeNumber(String activityName, String logSeparator) throws Exception {
+ return (new ActivityLifecycleCounts(activityName, logSeparator)).mConfigurationChangedCount;
+ }
+
+ /**
+ * Tests that when an activity is launched with displayId specified and there is an existing
+ * matching task on some other display - that task will moved to the target display.
+ */
+ @Test
+ public void testMoveToDisplayOnLaunch() throws Exception {
+ // Launch activity with unique affinity, so it will the only one in its task.
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+ // Launch something to that display so that a new stack is created. We need this to be
+ // able to compare task numbers in stacks later.
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
+
+ final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+ .mStacks.size();
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final int taskNumOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
+ .getTasks().size();
+
+ // Launch activity on new secondary display.
+ // Using custom command here, because normally we add flags
+ // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+ // when launching on some specific display. We don't do it here as we want an existing
+ // task to be used.
+ final String launchCommand = "am start -n " + getActivityComponentName(
+ LAUNCHING_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(launchCommand);
+ mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_RESUMED);
+
+ // Check that activity is brought to front.
+ mAmWmState.assertFocusedActivity("Existing task must be brought to front",
+ LAUNCHING_ACTIVITY);
+ mAmWmState.assertResumedActivity("Existing task must be resumed", LAUNCHING_ACTIVITY);
+
+ // Check that activity is on the right display.
+ final ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity must be moved to the secondary display",
+ getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // Check that task has moved from primary display to secondary.
+ final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+ .mStacks.size();
+ assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
+ stackNumFinal);
+ final int taskNumFinalOnSecondary = mAmWmState.getAmState().getStackById(frontStackId)
+ .getTasks().size();
+ assertEquals("Task number in stack on external display must be incremented.",
+ taskNumOnSecondary + 1, taskNumFinalOnSecondary);
+ }
+ }
+
+ /**
+ * Tests that when an activity is launched with displayId specified and there is an existing
+ * matching task on some other display - that task will moved to the target display.
+ */
+ @Test
+ public void testMoveToEmptyDisplayOnLaunch() throws Exception {
+ // Launch activity with unique affinity, so it will the only one in its task.
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ final int stackNum = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+ .mStacks.size();
+
+ // Launch activity on new secondary display.
+ // Using custom command here, because normally we add flags
+ // {@link Intent#FLAG_ACTIVITY_NEW_TASK} and {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+ // when launching on some specific display. We don't do it here as we want an existing
+ // task to be used.
+ final String launchCommand = "am start -n " + getActivityComponentName(
+ LAUNCHING_ACTIVITY)
+ + " --display " + newDisplay.mId;
+ executeShellCommand(launchCommand);
+ mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_RESUMED);
+
+ // Check that activity is brought to front.
+ mAmWmState.assertFocusedActivity("Existing task must be brought to front",
+ LAUNCHING_ACTIVITY);
+ mAmWmState.assertResumedActivity("Existing task must be resumed", LAUNCHING_ACTIVITY);
+
+ // Check that activity is on the right display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity must be moved to the secondary display",
+ getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // Check that task has moved from primary display to secondary.
+ final int stackNumFinal = mAmWmState.getAmState().getDisplay(DEFAULT_DISPLAY_ID)
+ .mStacks.size();
+ assertEquals("Stack number in default stack must be decremented.", stackNum - 1,
+ stackNumFinal);
+ }
+ }
+
+ /**
+ * Tests that when primary display is rotated secondary displays are not affected.
+ */
+ @Test
+ public void testRotationNotAffectingSecondaryScreen() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display.
+ final ActivityDisplay newDisplay = virtualDisplaySession.setResizeDisplay(false)
+ .createDisplay();
+ mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
+
+ // Launch activity on new secondary display.
+ String logSeparator = clearLogcat();
+ launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ RESIZEABLE_ACTIVITY_NAME);
+ final ReportedSizes initialSizes = getLastReportedSizesForActivity(
+ RESIZEABLE_ACTIVITY_NAME, logSeparator);
+ assertNotNull("Test activity must have reported initial sizes on launch", initialSizes);
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ // Rotate primary display and check that activity on secondary display is not
+ // affected.
+
+ rotateAndCheckSameSizes(rotationSession, RESIZEABLE_ACTIVITY_NAME);
+
+ // Launch activity to secondary display when primary one is rotated.
+ final int initialRotation = mAmWmState.getWmState().getRotation();
+ rotationSession.set((initialRotation + 1) % 4);
+
+ logSeparator = clearLogcat();
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+ mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
+ mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+ TEST_ACTIVITY_NAME);
+ final ReportedSizes testActivitySizes = getLastReportedSizesForActivity(
+ TEST_ACTIVITY_NAME, logSeparator);
+ assertEquals(
+ "Sizes of secondary display must not change after rotation of primary "
+ + "display",
+ initialSizes, testActivitySizes);
+ }
+ }
+ }
+
+ private void rotateAndCheckSameSizes(RotationSession rotationSession, String activityName)
+ throws Exception {
+ for (int rotation = 3; rotation >= 0; --rotation) {
+ final String logSeparator = clearLogcat();
+ rotationSession.set(rotation);
+ final ReportedSizes rotatedSizes = getLastReportedSizesForActivity(activityName,
+ logSeparator);
+ assertNull("Sizes must not change after rotation", rotatedSizes);
+ }
+ }
+
+ /**
+ * Tests that task affinity does affect what display an activity is launched on but that
+ * matching the task component root does.
+ */
+ @Test
+ public void testTaskMatchAcrossDisplays() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+
+ // Check that activity is on the secondary display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ executeShellCommand("am start -n " + getActivityComponentName(ALT_LAUNCHING_ACTIVITY));
+ mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, componentName,
+ new WaitForValidActivityState(ALT_LAUNCHING_ACTIVITY));
+
+ // Check that second activity gets launched on the default display despite
+ // the affinity match on the secondary display.
+ final int defaultDisplayFrontStackId = mAmWmState.getAmState().getFrontStackId(
+ DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack defaultDisplayFrontStack =
+ mAmWmState.getAmState().getStackById(defaultDisplayFrontStackId);
+ assertEquals("Activity launched on default display must be resumed",
+ getActivityComponentName(ALT_LAUNCHING_ACTIVITY),
+ defaultDisplayFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on primary display",
+ defaultDisplayFrontStackId);
+
+ executeShellCommand("am start -n " + getActivityComponentName(LAUNCHING_ACTIVITY));
+ mAmWmState.waitForFocusedStack(frontStackId);
+
+ // Check that the third intent is redirected to the first task due to the root
+ // component match on the secondary display.
+ final ActivityManagerState.ActivityStack secondFrontStack
+ = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(LAUNCHING_ACTIVITY),
+ secondFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on primary display", frontStackId);
+ assertEquals("Focused stack must only contain 1 task",
+ 1, secondFrontStack.getTasks().size());
+ assertEquals("Focused task must only contain 1 activity",
+ 1, secondFrontStack.getTasks().get(0).mActivities.size());
+ }
+ }
+
+ /**
+ * Tests that the task affinity search respects the launch display id.
+ */
+ @Test
+ public void testLaunchDisplayAffinityMatch() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
+
+ // Check that activity is on the secondary display.
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ // We don't want FLAG_ACTIVITY_MULTIPLE_TASK, so we can't use launchActivityOnDisplay
+ executeShellCommand("am start -n "
+ + getActivityComponentName(ALT_LAUNCHING_ACTIVITY)
+ + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK
+ + " --display " + newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(ALT_LAUNCHING_ACTIVITY));
+
+ // Check that second activity gets launched into the affinity matching
+ // task on the secondary display
+ final int secondFrontStackId =
+ mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack secondFrontStack =
+ mAmWmState.getAmState().getStackById(secondFrontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(ALT_LAUNCHING_ACTIVITY),
+ secondFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display",
+ secondFrontStackId);
+ assertEquals("Focused stack must only contain 1 task",
+ 1, secondFrontStack.getTasks().size());
+ assertEquals("Focused task must contain 2 activities",
+ 2, secondFrontStack.getTasks().get(0).mActivities.size());
+ }
+ }
+
+ /**
+ * Tests than a new task launched by an activity will end up on that activity's display
+ * even if the focused stack is not on that activity's display.
+ */
+ @Test
+ public void testNewTaskSameDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
+ .createDisplay();
+
+ launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+ mAmWmState.computeState(new WaitForValidActivityState(BROADCAST_RECEIVER_ACTIVITY));
+
+ // Check that the first activity is launched onto the secondary display
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(BROADCAST_RECEIVER_ACTIVITY),
+ firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+ executeShellCommand("am start -n " + getActivityComponentName(TEST_ACTIVITY_NAME));
+ mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, componentName,
+ new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+
+ // Check that the second activity is launched on the default display
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Activity launched on default display must be resumed",
+ getActivityComponentName(TEST_ACTIVITY_NAME), focusedStack.mResumedActivity);
+ assertEquals("Focus must be on primary display", DEFAULT_DISPLAY_ID,
+ focusedStack.mDisplayId);
+
+ executeShellCommand("am broadcast -a trigger_broadcast --ez launch_activity true "
+ + "--ez new_task true --es target_activity " + LAUNCHING_ACTIVITY);
+
+ // Check that the third activity ends up in a new task in the same stack as the
+ // first activity
+ mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, componentName,
+ new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+ final ActivityManagerState.ActivityStack secondFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity must be launched on secondary display",
+ getActivityComponentName(LAUNCHING_ACTIVITY),
+ secondFrontStack.mResumedActivity);
+ assertEquals("Secondary display must contain 2 tasks",
+ 2, secondFrontStack.getTasks().size());
+ }
+ }
+
+ /**
+ * Tests than an immediate launch after new display creation is handled correctly.
+ */
+ @Test
+ public void testImmediateLaunchOnNewDisplay() throws Exception {
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new virtual display and immediately launch an activity on it.
+ final ActivityDisplay newDisplay = virtualDisplaySession
+ .setLaunchActivity(TEST_ACTIVITY_NAME)
+ .createDisplay();
+
+ // Check that activity is launched and placed correctly.
+ mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
+ mAmWmState.assertResumedActivity("Test activity must be launched on a new display",
+ TEST_ACTIVITY_NAME);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+ final ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Activity launched on secondary display must be resumed",
+ getActivityComponentName(TEST_ACTIVITY_NAME), firstFrontStack.mResumedActivity);
+ mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+ }
+ }
+
+ /**
+ * Tests that turning the primary display off does not affect the activity running
+ * on an external secondary display.
+ */
+ @Test
+ public void testExternalDisplayActivityTurnPrimaryOff() throws Exception {
+ // Launch something on the primary display so we know there is a resumed activity there
+ launchActivity(RESIZEABLE_ACTIVITY_NAME);
+ waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
+ "Activity launched on primary display must be resumed");
+
+ try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
+ final PrimaryDisplayStateSession displayStateSession =
+ new PrimaryDisplayStateSession()) {
+ final ActivityDisplay newDisplay =
+ externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+ // Check that the activity is launched onto the external display
+ waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+
+ displayStateSession.turnScreenOff();
+
+ // Wait for the fullscreen stack to start sleeping, and then make sure the
+ // test activity is still resumed.
+ waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
+ "Activity launched on primary display must be stopped after turning off");
+ waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+ }
+ }
+
+ /**
+ * Tests that an activity can be launched on a secondary display while the primary
+ * display is off.
+ */
+ @Test
+ public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception {
+ // Launch something on the primary display so we know there is a resumed activity there
+ launchActivity(RESIZEABLE_ACTIVITY_NAME);
+ waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
+ "Activity launched on primary display must be resumed");
+
+ try (final PrimaryDisplayStateSession displayStateSession =
+ new PrimaryDisplayStateSession();
+ final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+ displayStateSession.turnScreenOff();
+
+ // Make sure there is no resumed activity when the primary display is off
+ waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
+ "Activity launched on primary display must be stopped after turning off");
+ assertEquals("Unexpected resumed activity",
+ 0, mAmWmState.getAmState().getResumedActivitiesCount());
+
+ final ActivityDisplay newDisplay =
+ externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+ // Check that the test activity is resumed on the external display
+ waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+ }
+ }
+
+ /**
+ * Tests that turning the secondary display off stops activities running on that display.
+ */
+ @Test
+ public void testExternalDisplayToggleState() throws Exception {
+ try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+ final ActivityDisplay newDisplay =
+ externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+ // Check that the test activity is resumed on the external display
+ waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+
+ externalDisplaySession.turnDisplayOff();
+
+ // Check that turning off the external display stops the activity
+ waitAndAssertActivityStopped(TEST_ACTIVITY_NAME,
+ "Activity launched on external display must be stopped after turning off");
+
+ externalDisplaySession.turnDisplayOn();
+
+ // Check that turning on the external display resumes the activity
+ waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+ }
+ }
+
+ /**
+ * Tests that tapping on the primary display after showing the keyguard resumes the
+ * activity on the primary display.
+ */
+ @Test
+ public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception {
+ // Launch something on the primary display so we know there is a resumed activity there
+ launchActivity(RESIZEABLE_ACTIVITY_NAME);
+ waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
+ "Activity launched on primary display must be resumed");
+
+ sleepDevice();
+
+ // Make sure there is no resumed activity when the primary display is off
+ waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
+ "Activity launched on primary display must be stopped after turning off");
+ assertEquals("Unexpected resumed activity",
+ 0, mAmWmState.getAmState().getResumedActivitiesCount());
+
+ try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+ final ActivityDisplay newDisplay =
+ externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+ // Check that the test activity is resumed on the external display
+ waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+ "Activity launched on external display must be resumed");
+
+ // Unlock the device and tap on the middle of the primary display
+ wakeUpDevice();
+ executeShellCommand("wm dismiss-keyguard");
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.waitForValidState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+ final ReportedDisplayMetrics displayMetrics = getDisplayMetrics();
+ final int width = displayMetrics.getSize().getWidth();
+ final int height = displayMetrics.getSize().getHeight();
+ executeShellCommand("input tap " + (width / 2) + " " + (height / 2));
+
+ // Check that the activity on the primary display is resumed
+ waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, DEFAULT_DISPLAY_ID,
+ "Activity launched on primary display must be resumed");
+ assertEquals("Unexpected resumed activity",
+ 1, mAmWmState.getAmState().getResumedActivitiesCount());
+ }
+ }
+
+ private void waitAndAssertActivityResumed(String activityName, int displayId, String message)
+ throws Exception {
+ mAmWmState.waitForActivityState(activityName, STATE_RESUMED);
+
+ final String fullActivityName = getActivityComponentName(activityName);
+ assertEquals(message, fullActivityName, mAmWmState.getAmState().getResumedActivity());
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
+ ActivityManagerState.ActivityStack firstFrontStack =
+ mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals(message, fullActivityName, firstFrontStack.mResumedActivity);
+ assertTrue(message,
+ mAmWmState.getAmState().hasActivityState(activityName, STATE_RESUMED));
+ mAmWmState.assertFocusedStack("Focus must be on external display", frontStackId);
+ mAmWmState.assertVisibility(activityName, true /* visible */);
+ }
+
+ private void waitAndAssertActivityStopped(String activityName, String message)
+ throws Exception {
+ mAmWmState.waitForActivityState(activityName, STATE_STOPPED);
+
+ assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName,
+ STATE_STOPPED));
+ }
+
+ /**
+ * Tests that showWhenLocked works on a secondary display.
+ */
+ public void testSecondaryDisplayShowWhenLocked() throws Exception {
+ try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+ setLockCredential();
+
+ launchActivity(TEST_ACTIVITY_NAME);
+
+ final ActivityDisplay newDisplay =
+ externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+ launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, newDisplay.mId);
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+
+ mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_STOPPED);
+ mAmWmState.waitForActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED);
+
+ mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME);
+ assertTrue("Expected resumed activity on secondary display", mAmWmState.getAmState()
+ .hasActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED));
+ } finally {
+ tearDownLockCredentials();
+ }
+ }
+
+ /** Assert that component received onMovedToDisplay and onConfigurationChanged callbacks. */
+ private void assertMovedToDisplay(String componentName, String logSeparator) throws Exception {
+ final ActivityLifecycleCounts lifecycleCounts
+ = new ActivityLifecycleCounts(componentName, logSeparator);
+ if (lifecycleCounts.mDestroyCount != 0) {
+ fail(componentName + " has been destroyed " + lifecycleCounts.mDestroyCount
+ + " time(s), wasn't expecting any");
+ } else if (lifecycleCounts.mCreateCount != 0) {
+ fail(componentName + " has been (re)created " + lifecycleCounts.mCreateCount
+ + " time(s), wasn't expecting any");
+ } else if (lifecycleCounts.mConfigurationChangedCount != 1) {
+ fail(componentName + " has received "
+ + lifecycleCounts.mConfigurationChangedCount
+ + " onConfigurationChanged() calls, expecting " + 1);
+ } else if (lifecycleCounts.mMovedToDisplayCount != 1) {
+ fail(componentName + " has received "
+ + lifecycleCounts.mMovedToDisplayCount
+ + " onMovedToDisplay() calls, expecting " + 1);
+ }
+ }
+
+ private static String getResizeVirtualDisplayCommand() {
+ return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" +
+ " --es command resize_display";
+ }
+
+ private class ExternalDisplaySession implements AutoCloseable {
+
+ @Nullable
+ private DisplayHelper mExternalDisplayHelper;
+
+ /**
+ * Creates a private virtual display with the external and show with insecure
+ * keyguard flags set.
+ */
+ ActivityDisplay createVirtualDisplay(boolean showContentWhenLocked)
+ throws Exception {
+ final List<ActivityDisplay> originalDS = getDisplaysStates();
+ final int originalDisplayCount = originalDS.size();
+
+ mExternalDisplayHelper = new DisplayHelper();
+ mExternalDisplayHelper.createAndWaitForDisplay(true /* external */,
+ showContentWhenLocked);
+
+ // Wait for the virtual display to be created and get configurations.
+ final List<ActivityDisplay> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
+ assertEquals("New virtual display must be created", originalDisplayCount + 1,
+ ds.size());
+
+ // Find the newly added display.
+ final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds);
+ return newDisplays.get(0);
+ }
+
+ void turnDisplayOff() {
+ if (mExternalDisplayHelper == null) {
+ new RuntimeException("No external display created");
+ }
+ mExternalDisplayHelper.turnDisplayOff();
+ }
+
+ void turnDisplayOn() {
+ if (mExternalDisplayHelper == null) {
+ new RuntimeException("No external display created");
+ }
+ mExternalDisplayHelper.turnDisplayOn();
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mExternalDisplayHelper != null) {
+ mExternalDisplayHelper.releaseDisplay();
+ mExternalDisplayHelper = null;
+ }
+ }
+ }
+
+ private static class PrimaryDisplayStateSession implements AutoCloseable {
+
+ void turnScreenOff() {
+ setPrimaryDisplayState(false);
+ }
+
+ @Override
+ public void close() throws Exception {
+ setPrimaryDisplayState(true);
+ }
+
+ /** Turns the primary display on/off by pressing the power key */
+ private void setPrimaryDisplayState(boolean wantOn) {
+ // Either KeyEvent.KEYCODE_WAKEUP or KeyEvent.KEYCODE_SLEEP
+ int keycode = wantOn ? 224 : 223;
+ executeShellCommand("input keyevent " + keycode);
+ DisplayHelper.waitForDefaultDisplayState(wantOn);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
new file mode 100644
index 0000000..f5a6a22
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
@@ -0,0 +1,1553 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+import static android.server.am.ActivityManagerState.STATE_DESTROYED;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.view.KeyEvent.KEYCODE_WINDOW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+import android.server.am.settings.SettingsSession;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerPinnedStackTests
+ */
+public class ActivityManagerPinnedStackTests extends ActivityManagerTestBase {
+ private static final String TEST_ACTIVITY = "TestActivity";
+ private static final String TEST_ACTIVITY_WITH_SAME_AFFINITY = "TestActivityWithSameAffinity";
+ private static final String TRANSLUCENT_TEST_ACTIVITY = "TranslucentTestActivity";
+ private static final String NON_RESIZEABLE_ACTIVITY = "NonResizeableActivity";
+ private static final String RESUME_WHILE_PAUSING_ACTIVITY = "ResumeWhilePausingActivity";
+ private static final String PIP_ACTIVITY = "PipActivity";
+ private static final String PIP_ACTIVITY2 = "PipActivity2";
+ private static final String PIP_ACTIVITY_WITH_SAME_AFFINITY = "PipActivityWithSameAffinity";
+ private static final String ALWAYS_FOCUSABLE_PIP_ACTIVITY = "AlwaysFocusablePipActivity";
+ private static final String LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY =
+ "LaunchIntoPinnedStackPipActivity";
+ private static final String LAUNCH_ENTER_PIP_ACTIVITY = "LaunchEnterPipActivity";
+ private static final String PIP_ON_STOP_ACTIVITY = "PipOnStopActivity";
+
+ private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+ private static final String EXTRA_ENTER_PIP = "enter_pip";
+ private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR =
+ "enter_pip_aspect_ratio_numerator";
+ private static final String EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR =
+ "enter_pip_aspect_ratio_denominator";
+ private static final String EXTRA_SET_ASPECT_RATIO_NUMERATOR = "set_aspect_ratio_numerator";
+ private static final String EXTRA_SET_ASPECT_RATIO_DENOMINATOR = "set_aspect_ratio_denominator";
+ private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR =
+ "set_aspect_ratio_with_delay_numerator";
+ private static final String EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR =
+ "set_aspect_ratio_with_delay_denominator";
+ private static final String EXTRA_ENTER_PIP_ON_PAUSE = "enter_pip_on_pause";
+ private static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+ private static final String EXTRA_START_ACTIVITY = "start_activity";
+ private static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
+ private static final String EXTRA_REENTER_PIP_ON_EXIT = "reenter_pip_on_exit";
+ private static final String EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP = "assert_no_on_stop_before_pip";
+ private static final String EXTRA_ON_PAUSE_DELAY = "on_pause_delay";
+
+ private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
+ "android.server.am.PipActivity.enter_pip";
+ private static final String PIP_ACTIVITY_ACTION_MOVE_TO_BACK =
+ "android.server.am.PipActivity.move_to_back";
+ private static final String PIP_ACTIVITY_ACTION_EXPAND_PIP =
+ "android.server.am.PipActivity.expand_pip";
+ private static final String PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION =
+ "android.server.am.PipActivity.set_requested_orientation";
+ private static final String PIP_ACTIVITY_ACTION_FINISH =
+ "android.server.am.PipActivity.finish";
+ private static final String TEST_ACTIVITY_ACTION_FINISH =
+ "android.server.am.TestActivity.finish_self";
+
+ private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
+ private static final int APP_OPS_MODE_ALLOWED = 0;
+ private static final int APP_OPS_MODE_IGNORED = 1;
+ private static final int APP_OPS_MODE_ERRORED = 2;
+
+ private static final int ROTATION_0 = 0;
+ private static final int ROTATION_90 = 1;
+ private static final int ROTATION_180 = 2;
+ private static final int ROTATION_270 = 3;
+
+ // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ private static final int ORIENTATION_LANDSCAPE = 0;
+ // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ private static final int ORIENTATION_PORTRAIT = 1;
+
+ private static final float FLOAT_COMPARE_EPSILON = 0.005f;
+
+ // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
+ private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
+ private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
+ private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
+ // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
+ private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
+ private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
+ private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
+
+ @Test
+ public void testMinimumDeviceSize() throws Exception {
+ assumeTrue(supportsPip());
+
+ mAmWmState.assertDeviceDefaultDisplaySize(
+ "Devices supporting picture-in-picture must be larger than the default minimum"
+ + " task size");
+ }
+
+ @Presubmit
+ @Test
+ public void testEnterPictureInPictureMode() throws Exception {
+ pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"),
+ PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
+ false /* isFocusable */);
+ }
+
+ @FlakyTest(bugId = 71444628)
+ @Presubmit
+ @Test
+ public void testMoveTopActivityToPinnedStack() throws Exception {
+ pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
+ true /* moveTopToPinnedStack */, false /* isFocusable */);
+ }
+
+ // This test is back-listed in cts-known-failures.xml.
+ @Test
+ public void testAlwaysFocusablePipActivity() throws Exception {
+ pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
+ ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
+ false /* moveTopToPinnedStack */, true /* isFocusable */);
+ }
+
+ // This test is back-listed in cts-known-failures.xml.
+ @Presubmit
+ @Test
+ public void testLaunchIntoPinnedStack() throws Exception {
+ pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
+ LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
+ false /* moveTopToPinnedStack */, true /* isFocusable */);
+ }
+
+ @Test
+ public void testNonTappablePipActivity() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch the tap-to-finish activity at a specific place
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_TAP_TO_FINISH, "true");
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForAppTransitionIdle();
+ assertPinnedStackExists();
+
+ // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
+ // not passed down to the top task
+ tapToFinishPip();
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(PIP_ACTIVITY).build());
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+ }
+
+ @Test
+ public void testPinnedStackDefaultBounds() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a PIP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+
+ WindowManagerState wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ Rect defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+ Rect stableBounds = wmState.getStableBounds();
+ assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+ assertTrue(stableBounds.contains(defaultPipBounds));
+
+ rotationSession.set(ROTATION_90);
+ wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+ stableBounds = wmState.getStableBounds();
+ assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+ assertTrue(stableBounds.contains(defaultPipBounds));
+ }
+ }
+
+ @Test
+ public void testPinnedStackMovementBounds() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a PIP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+ WindowManagerState wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ Rect pipMovementBounds = wmState.getPinnedStackMomentBounds();
+ Rect stableBounds = wmState.getStableBounds();
+ assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+ assertTrue(stableBounds.contains(pipMovementBounds));
+
+ rotationSession.set(ROTATION_90);
+ wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ pipMovementBounds = wmState.getPinnedStackMomentBounds();
+ stableBounds = wmState.getStableBounds();
+ assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+ assertTrue(stableBounds.contains(pipMovementBounds));
+ }
+ }
+
+ @Test
+ @FlakyTest // TODO: Reintroduce to presubmit once b/71508234 is resolved.
+ public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
+ assumeTrue(supportsPip());
+
+ final WindowManagerState wmState = mAmWmState.getWmState();
+
+ // Launch an activity into the pinned stack
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_TAP_TO_FINISH, "true");
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForAppTransitionIdle();
+
+ // Get the display dimensions
+ WindowManagerState.WindowState windowState = getWindowState(PIP_ACTIVITY);
+ WindowManagerState.Display display = wmState.getDisplay(windowState.getDisplayId());
+ Rect displayRect = display.getDisplayRect();
+
+ // Move the pinned stack offscreen
+ final int stackId = getPinnedStack().mStackId;
+ final int top = 0;
+ final int left = displayRect.width() - 200;
+ resizeStack(stackId, left, top, left + 500, top + 500);
+
+ // Ensure that the surface insets are not negative
+ windowState = getWindowState(PIP_ACTIVITY);
+ Rect contentInsets = windowState.getContentInsets();
+ if (contentInsets != null) {
+ assertTrue(contentInsets.left >= 0 && contentInsets.top >= 0
+ && contentInsets.width() >= 0 && contentInsets.height() >= 0);
+ }
+ }
+
+ @Test
+ public void testPinnedStackInBoundsAfterRotation() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch an activity into the pinned stack
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_TAP_TO_FINISH, "true");
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ // Ensure that the PIP stack is fully visible in each orientation
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_0);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ rotationSession.set(ROTATION_90);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ rotationSession.set(ROTATION_180);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ rotationSession.set(ROTATION_270);
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ }
+ }
+
+ @Test
+ public void testEnterPipToOtherOrientation() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a portrait only app on the fullscreen stack
+ launchActivity(TEST_ACTIVITY,
+ EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
+ // Launch the PiP activity fixed as landscape
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE));
+ // Enter PiP, and assert that the PiP is within bounds now that the device is back in
+ // portrait
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+ assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
+ }
+
+ @Test
+ public void testEnterPipAspectRatioMin() throws Exception {
+ testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testEnterPipAspectRatioMax() throws Exception {
+ testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ private void testEnterPipAspectRatio(int num, int denom) throws Exception {
+ assumeTrue(supportsPip());
+
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+ EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+ assertPinnedStackExists();
+
+ // Assert that we have entered PIP and that the aspect ratio is correct
+ Rect pinnedStackBounds = getPinnedStackBounds();
+ assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
+ (float) num / denom);
+ }
+
+ @Test
+ public void testResizePipAspectRatioMin() throws Exception {
+ testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testResizePipAspectRatioMax() throws Exception {
+ testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ private void testResizePipAspectRatio(int num, int denom) throws Exception {
+ assumeTrue(supportsPip());
+
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+ EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+ assertPinnedStackExists();
+ waitForValidAspectRatio(num, denom);
+ Rect bounds = getPinnedStackBounds();
+ assertFloatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
+ }
+
+ @Test
+ public void testEnterPipExtremeAspectRatioMin() throws Exception {
+ testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
+ BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testEnterPipExtremeAspectRatioMax() throws Exception {
+ testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
+ MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ private void testEnterPipExtremeAspectRatio(int num, int denom) throws Exception {
+ assumeTrue(supportsPip());
+
+ // Assert that we could not create a pinned stack with an extreme aspect ratio
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+ EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ public void testSetPipExtremeAspectRatioMin() throws Exception {
+ testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
+ BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testSetPipExtremeAspectRatioMax() throws Exception {
+ testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
+ MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ private void testSetPipExtremeAspectRatio(int num, int denom) throws Exception {
+ assumeTrue(supportsPip());
+
+ // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
+ // fails (the aspect ratio remains the same)
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
+ Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
+ EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
+ Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
+ EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
+ EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+ assertPinnedStackExists();
+ Rect pinnedStackBounds = getPinnedStackBounds();
+ assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
+ (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch the bottom pip activity
+ launchActivity(PIP_ON_STOP_ACTIVITY);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ // Wait for the bottom pip activity to be stopped
+ mAmWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
+
+ // Assert that there is no pinned stack (that enterPictureInPicture() failed)
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ public void testAutoEnterPictureInPicture() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a test activity so that we're not over home
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch the PIP activity on pause
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+ assertPinnedStackDoesNotExist();
+
+ // Go home and ensure that there is a pinned stack
+ launchHomeActivity();
+ assertPinnedStackExists();
+ }
+
+ @Test
+ public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a test activity so that we're not over home
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch the PIP activity on pause, and have it start another activity on
+ // top of itself. Wait for the new activity to be visible and ensure that the pinned stack
+ // was not created in the process
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP_ON_PAUSE, "true",
+ EXTRA_START_ACTIVITY, getActivityComponentName(NON_RESIZEABLE_ACTIVITY));
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY).build());
+ assertPinnedStackDoesNotExist();
+
+ // Go home while the pip activity is open and ensure the previous activity is not PIPed
+ launchHomeActivity();
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ public void testAutoEnterPictureInPictureFinish() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a test activity so that we're not over home
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch the PIP activity on pause, and set it to finish itself after
+ // some period. Wait for the previous activity to be visible, and ensure that the pinned
+ // stack was not created in the process
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP_ON_PAUSE, "true",
+ EXTRA_FINISH_SELF_ON_RESUME, "true");
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Presubmit
+ @Test
+ public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch the PIP activity on pause, and set the aspect ratio
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP_ON_PAUSE, "true",
+ EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
+ EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
+
+ // Go home while the pip activity is open to trigger auto-PIP
+ launchHomeActivity();
+ assertPinnedStackExists();
+
+ waitForValidAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+ Rect bounds = getPinnedStackBounds();
+ assertFloatEquals((float) bounds.width() / bounds.height(),
+ (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Presubmit
+ @Test
+ public void testAutoEnterPictureInPictureOverPip() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch another PIP activity
+ launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+
+ // Launch the PIP activity on pause
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+
+ // Go home while the PIP activity is open to trigger auto-enter PIP
+ launchHomeActivity();
+ assertPinnedStackExists();
+
+ // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
+ // still the first activity
+ final ActivityStack pinnedStack = getPinnedStack();
+ assertTrue(pinnedStack.getTasks().size() == 1);
+ assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
+ ALWAYS_FOCUSABLE_PIP_ACTIVITY)));
+ }
+
+ @Presubmit
+ @Test
+ public void testDisallowMultipleTasksInPinnedStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a test activity so that we have multiple fullscreen tasks
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch first PIP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+ // Launch second PIP activity
+ launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
+
+ final ActivityStack pinnedStack = getPinnedStack();
+ assertEquals(1, pinnedStack.getTasks().size());
+ assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+ PIP_ACTIVITY2, WINDOWING_MODE_PINNED));
+ assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+ }
+
+ @Test
+ public void testPipUnPipOverHome() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Go home
+ launchHomeActivity();
+ // Launch an auto pip activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_REENTER_PIP_ON_EXIT, "true");
+ assertPinnedStackExists();
+
+ // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
+ launchActivity(PIP_ACTIVITY);
+ mAmWmState.waitForWithAmState((amState) -> {
+ return amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID)
+ == WINDOWING_MODE_FULLSCREEN;
+ }, "Waiting for PIP to exit to fullscreen");
+ mAmWmState.waitForWithAmState((amState) -> {
+ return amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID) == WINDOWING_MODE_PINNED;
+ }, "Waiting to re-enter PIP");
+ mAmWmState.assertHomeActivityVisible(true);
+ }
+
+ @Test
+ public void testPipUnPipOverApp() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a test activity so that we're not over home
+ launchActivity(TEST_ACTIVITY);
+
+ // Launch an auto pip activity
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_REENTER_PIP_ON_EXIT, "true");
+ assertPinnedStackExists();
+
+ // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
+ launchActivity(PIP_ACTIVITY);
+ mAmWmState.waitForWithAmState((amState) -> {
+ return amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID)
+ == WINDOWING_MODE_FULLSCREEN;
+ }, "Waiting for PIP to exit to fullscreen");
+ mAmWmState.waitForWithAmState((amState) -> {
+ return amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID) == WINDOWING_MODE_PINNED;
+ }, "Waiting to re-enter PIP");
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+ }
+
+ @Presubmit
+ @Test
+ public void testRemovePipWithNoFullscreenStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Start with a clean slate, remove all the stacks but home
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+
+ // Launch a pip activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is now in the fullscreen stack (when no
+ // fullscreen stack existed before)
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ @Presubmit
+ @Test
+ public void testRemovePipWithVisibleFullscreenStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a fullscreen activity, and a pip activity over that
+ launchActivity(TEST_ACTIVITY);
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
+ // top fullscreen activity
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @FlakyTest(bugId = 70746098)
+ @Presubmit
+ @Test
+ public void testRemovePipWithHiddenFullscreenStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
+ // launch a pip activity over home
+ launchActivity(TEST_ACTIVITY);
+ launchHomeActivity();
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
+ // stack, but that the home stack is still focused
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ @Test
+ public void testMovePipToBackWithNoFullscreenStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Start with a clean slate, remove all the stacks but home
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+
+ // Launch a pip activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is now in the fullscreen stack (when no
+ // fullscreen stack existed before)
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ @FlakyTest(bugId = 70906499)
+ @Presubmit
+ @Test
+ public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a fullscreen activity, and a pip activity over that
+ launchActivity(TEST_ACTIVITY);
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
+ // top fullscreen activity
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @FlakyTest(bugId = 70906499)
+ @Presubmit
+ @Test
+ public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
+ // launch a pip activity over home
+ launchActivity(TEST_ACTIVITY);
+ launchHomeActivity();
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
+ // stack, but that the home stack is still focused
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_MOVE_TO_BACK);
+ assertPinnedStackStateOnMoveToFullscreen(
+ PIP_ACTIVITY, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ @Test
+ public void testPinnedStackAlwaysOnTop() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch activity into pinned stack and assert it's on top.
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+ assertPinnedStackIsOnTop();
+
+ // Launch another activity in fullscreen stack and check that pinned stack is still on top.
+ launchActivity(TEST_ACTIVITY);
+ assertPinnedStackExists();
+ assertPinnedStackIsOnTop();
+
+ // Launch home and check that pinned stack is still on top.
+ launchHomeActivity();
+ assertPinnedStackExists();
+ assertPinnedStackIsOnTop();
+ }
+
+ @Test
+ public void testAppOpsDenyPipOnPause() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Disable enter-pip and try to enter pip
+ setAppOpsOpToMode(ActivityManagerTestBase.componentName,
+ APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
+
+ // Launch the PIP activity on pause
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackDoesNotExist();
+
+ // Go home and ensure that there is no pinned stack
+ launchHomeActivity();
+ assertPinnedStackDoesNotExist();
+
+ // Re-enable enter-pip-on-hide
+ setAppOpsOpToMode(ActivityManagerTestBase.componentName,
+ APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_ALLOWED);
+ }
+
+ @Test
+ public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Try to enter picture-in-picture from an activity that has more than one activity in the
+ // task and ensure that it works
+ launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+ }
+
+ @Test
+ public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
+ assumeTrue(supportsPip());
+
+ /*
+ * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
+ * stopped and actually went into the pinned stack.
+ *
+ * Note that this is a workaround because to trigger the path that we want to happen in
+ * activity manager, we need to add the leaving activity to the stopping state, which only
+ * happens when a hidden stack is brought forward. Normally, this happens when you go home,
+ * but since we can't launch into the home stack directly, we have a workaround.
+ *
+ * 1) Launch an activity in a new dynamic stack
+ * 2) Resize the dynamic stack to non-fullscreen bounds
+ * 3) Start the PiP activity that will enter picture-in-picture when paused in the
+ * fullscreen stack
+ * 4) Bring the activity in the dynamic stack forward to trigger PiP
+ */
+ int stackId = launchActivityInNewDynamicStack(RESUME_WHILE_PAUSING_ACTIVITY);
+ resizeStack(stackId, 0, 0, 500, 500);
+ // Launch an activity that will enter PiP when it is paused with a delay that is long enough
+ // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
+ // trigger the current system pause timeout (currently 500ms)
+ launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+ EXTRA_ENTER_PIP_ON_PAUSE, "true",
+ EXTRA_ON_PAUSE_DELAY, "350",
+ EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+ launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+ assertPinnedStackExists();
+ }
+
+ @Test
+ public void testDisallowEnterPipActivityLocked() throws Exception {
+ assumeTrue(supportsPip());
+
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+ ActivityTask task = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).getTopTask();
+
+ // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
+ // when paused
+ executeShellCommand("am task lock " + task.mTaskId);
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackDoesNotExist();
+ launchHomeActivity();
+ assertPinnedStackDoesNotExist();
+ executeShellCommand("am task lock stop");
+ }
+
+ @FlakyTest(bugId = 70328524)
+ @Presubmit
+ @Test
+ public void testConfigurationChangeOrderDuringTransition() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a PiP activity and ensure configuration change only happened once, and that the
+ // configuration change happened after the picture-in-picture and multi-window callbacks
+ launchActivity(PIP_ACTIVITY);
+ String logSeparator = clearLogcat();
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+ waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
+ assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
+
+ // Trigger it to go back to fullscreen and ensure that only triggered one configuration
+ // change as well
+ logSeparator = clearLogcat();
+ launchActivity(PIP_ACTIVITY);
+ waitForValidPictureInPictureCallbacks(PIP_ACTIVITY, logSeparator);
+ assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, logSeparator);
+ }
+
+ /** Helper class to save, set, and restore transition_animation_scale preferences. */
+ private static class TransitionAnimationScaleSession extends SettingsSession<Float> {
+ TransitionAnimationScaleSession() {
+ super(Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE),
+ Settings.Global::getFloat,
+ Settings.Global::putFloat);
+ }
+ }
+
+ @Test
+ public void testEnterPipInterruptedCallbacks() throws Exception {
+ assumeTrue(supportsPip());
+
+ try (final TransitionAnimationScaleSession transitionAnimationScaleSession =
+ new TransitionAnimationScaleSession()) {
+ // Slow down the transition animations for this test
+ transitionAnimationScaleSession.set(20f);
+
+ // Launch a PiP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ // Wait until the PiP activity has moved into the pinned stack (happens before the
+ // transition has started)
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+
+ // Relaunch the PiP activity back into fullscreen
+ String logSeparator = clearLogcat();
+ launchActivity(PIP_ACTIVITY);
+ // Wait until the PiP activity is reparented into the fullscreen stack (happens after
+ // the transition has finished)
+ mAmWmState.waitForValidState(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+
+ // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
+ // configuration change (since none was sent)
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
+ PIP_ACTIVITY, logSeparator);
+ assertTrue(lifecycleCounts.mConfigurationChangedCount == 0);
+ assertTrue(lifecycleCounts.mPictureInPictureModeChangedCount == 1);
+ assertTrue(lifecycleCounts.mMultiWindowModeChangedCount == 1);
+ }
+ }
+
+ @FlakyTest(bugId = 71564769)
+ @Presubmit
+ @Test
+ public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a PiP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Dismiss it
+ String logSeparator = clearLogcat();
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ mAmWmState.waitForValidState(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+
+ // Confirm that we get stop before the multi-window and picture-in-picture mode change
+ // callbacks
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
+ logSeparator);
+ if (lifecycleCounts.mStopCount != 1) {
+ fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mStopCount
+ + " onStop() calls, expecting 1");
+ } else if (lifecycleCounts.mPictureInPictureModeChangedCount != 1) {
+ fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mPictureInPictureModeChangedCount
+ + " onPictureInPictureModeChanged() calls, expecting 1");
+ } else if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
+ fail(PIP_ACTIVITY + " has received " + lifecycleCounts.mMultiWindowModeChangedCount
+ + " onMultiWindowModeChanged() calls, expecting 1");
+ } else {
+ int lastStopLine = lifecycleCounts.mLastStopLineIndex;
+ int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
+ int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
+ if (!(lastStopLine < lastPipLine && lastPipLine < lastMwLine)) {
+ fail(PIP_ACTIVITY + " has received callbacks in unexpected order. Expected:"
+ + " stop < pip < mw, but got line indices: " + lastStopLine + ", "
+ + lastPipLine + ", " + lastMwLine + " respectively");
+ }
+ }
+ }
+
+ @Test
+ public void testPreventSetAspectRatioWhileExpanding() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch the PiP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+ // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
+ // call to set the aspect ratio did not prevent the PiP from returning to fullscreen
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_EXPAND_PIP
+ + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR + " 123456789"
+ + " -e " + EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR + " 100000000");
+ mAmWmState.waitForValidState(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ public void testSetRequestedOrientationWhilePinned() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch the PiP activity fixed as portrait, and enter picture-in-picture
+ launchActivity(PIP_ACTIVITY,
+ EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
+ EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+
+ // Request that the orientation is set to landscape
+ executeShellCommand("am broadcast -a "
+ + PIP_ACTIVITY_ACTION_SET_REQUESTED_ORIENTATION + " -e "
+ + EXTRA_FIXED_ORIENTATION + " " + String.valueOf(ORIENTATION_LANDSCAPE));
+
+ // Launch the activity back into fullscreen and ensure that it is now in landscape
+ launchActivity(PIP_ACTIVITY);
+ mAmWmState.waitForValidState(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackDoesNotExist();
+ assertTrue(mAmWmState.getWmState().getLastOrientation() == ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ public void testWindowButtonEntersPip() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch the PiP activity trigger the window button, ensure that we have entered PiP
+ launchActivity(PIP_ACTIVITY);
+ pressWindowButton();
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+ }
+
+ @Test
+ public void testFinishPipActivityWithTaskOverlay() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch PiP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+ int taskId = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getTopTask().mTaskId;
+
+ // Ensure that we don't any any other overlays as a result of launching into PIP
+ launchHomeActivity();
+
+ // Launch task overlay activity into PiP activity task
+ launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
+
+ // Finish the PiP activity and ensure that there is no pinned stack
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_FINISH);
+ mAmWmState.waitForWithAmState((amState) -> {
+ return getPinnedStack() == null;
+ }, "Waiting for pinned stack to be removed...");
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch PiP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+ int taskId = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getTopTask().mTaskId;
+
+ // Launch task overlay activity into PiP activity task
+ launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
+
+ // Finish the task overlay activity while animating and ensure that the PiP activity never
+ // got resumed
+ String logSeparator = clearLogcat();
+ executeShellCommand("am stack resize-animated 4 20 20 500 500");
+ executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
+ mAmWmState.waitFor((amState, wmState) -> !amState.containsActivity(
+ TRANSLUCENT_TEST_ACTIVITY), "Waiting for test activity to finish...");
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY,
+ logSeparator);
+ assertTrue(lifecycleCounts.mResumeCount == 0);
+ assertTrue(lifecycleCounts.mPauseCount == 0);
+ }
+
+ @Test
+ public void testPinnedStackWithDockedStack() throws Exception {
+ assumeTrue(supportsPip());
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY).setRandomData(true)
+ .setMultipleTask(false)
+ );
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+ // Launch the activities again to take focus and make sure nothing is hidden
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivityName(TEST_ACTIVITY).setRandomData(true)
+ .setMultipleTask(false)
+ );
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+ // Go to recents to make sure that fullscreen stack is invisible
+ // Some devices do not support recents or implement it differently (instead of using a
+ // separate stack id or as an activity), for those cases the visibility asserts will be
+ // ignored
+ pressAppSwitchButton();
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+ }
+
+ @Test
+ public void testLaunchTaskByComponentMatchMultipleTasks() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
+ // affinity
+ launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+ launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
+ assertPinnedStackExists();
+
+ // Launch the root activity again...
+ int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
+ launchHomeActivity();
+ launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+
+ // ...and ensure that the root activity task is found and reused, and that the pinned stack
+ // is unaffected
+ assertPinnedStackExists();
+ mAmWmState.assertFocusedActivity("Expected root activity focused",
+ TEST_ACTIVITY_WITH_SAME_AFFINITY);
+ assertTrue(rootActivityTaskId == mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
+ }
+
+ @Test
+ public void testLaunchTaskByAffinityMatchMultipleTasks() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
+ // affinity, and also launch another activity in the same task, while finishing itself. As
+ // a result, the task will not have a component matching the same activity as what it was
+ // started with
+ launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
+ EXTRA_START_ACTIVITY, getActivityComponentName(TEST_ACTIVITY),
+ EXTRA_FINISH_SELF_ON_RESUME, "true");
+ mAmWmState.waitForValidState(
+ TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
+ mAmWmState.waitForValidState(
+ PIP_ACTIVITY_WITH_SAME_AFFINITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+
+ // Launch the root activity again...
+ int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY).mTaskId;
+ launchHomeActivity();
+ launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+
+ // ...and ensure that even while matching purely by task affinity, the root activity task is
+ // found and reused, and that the pinned stack is unaffected
+ assertPinnedStackExists();
+ mAmWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
+ assertTrue(rootActivityTaskId == mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY).mTaskId);
+ }
+
+ @Test
+ public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Launch an activity into the pinned stack with a fixed affinity
+ launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
+ EXTRA_ENTER_PIP, "true",
+ EXTRA_START_ACTIVITY, getActivityComponentName(PIP_ACTIVITY),
+ EXTRA_FINISH_SELF_ON_RESUME, "true");
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+
+ // Launch the root activity again, of the matching task and ensure that we expand to
+ // fullscreen
+ int activityTaskId = mAmWmState.getAmState().getTaskByActivityName(
+ PIP_ACTIVITY).mTaskId;
+ launchHomeActivity();
+ launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
+ mAmWmState.waitForValidState(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackDoesNotExist();
+ assertTrue(activityTaskId == mAmWmState.getAmState().getTaskByActivityName(
+ PIP_ACTIVITY).mTaskId);
+ }
+
+ /** Test that reported display size corresponds to fullscreen after exiting PiP. */
+ @FlakyTest
+ @Presubmit
+ @Test
+ public void testDisplayMetricsPinUnpin() throws Exception {
+ assumeTrue(supportsPip());
+
+ String logSeparator = clearLogcat();
+ launchActivity(TEST_ACTIVITY);
+ final int defaultWindowingMode = mAmWmState.getAmState()
+ .getTaskByActivityName(TEST_ACTIVITY).getWindowingMode();
+ final ReportedSizes initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY,
+ logSeparator);
+ final Rect initialAppBounds = readAppBounds(TEST_ACTIVITY, logSeparator);
+ assertNotNull("Must report display dimensions", initialSizes);
+ assertNotNull("Must report app bounds", initialAppBounds);
+
+ logSeparator = clearLogcat();
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForAppTransitionIdle();
+ final ReportedSizes pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
+ logSeparator);
+ final Rect pinnedAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
+ assertFalse("Reported display size when pinned must be different from default",
+ initialSizes.equals(pinnedSizes));
+ assertFalse("Reported app bounds when pinned must be different from default",
+ initialAppBounds.width() == pinnedAppBounds.width()
+ && initialAppBounds.height() == pinnedAppBounds.height());
+
+ logSeparator = clearLogcat();
+ launchActivity(PIP_ACTIVITY, defaultWindowingMode);
+ final ReportedSizes finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
+ logSeparator);
+ final Rect finalAppBounds = readAppBounds(PIP_ACTIVITY, logSeparator);
+ assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
+ assertEquals("Must report default app width() after exiting PiP", initialAppBounds.width(),
+ finalAppBounds.width());
+ assertEquals("Must report default app height() after exiting PiP", initialAppBounds.height(),
+ finalAppBounds.height());
+ }
+
+ @Presubmit
+ @Test
+ public void testEnterPictureInPictureSavePosition() throws Exception {
+ if (!supportsPip()) return;
+
+ // Launch PiP activity with auto-enter PiP, save the default position of the PiP
+ // (while the PiP is still animating sleep)
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ mAmWmState.waitForAppTransitionIdle();
+ assertPinnedStackExists();
+
+ // Move the PiP to a new position on screen
+ final int stackId = getPinnedStack().mStackId;
+ final Rect initialDefaultBounds = mAmWmState.getWmState().getDefaultPinnedStackBounds();
+ final Rect offsetStackBounds = getPinnedStackBounds();
+ offsetStackBounds.offset(0, -100);
+ resizeStack(stackId, offsetStackBounds.left, offsetStackBounds.top, offsetStackBounds.right,
+ offsetStackBounds.bottom);
+
+ // Expand the PiP back to fullscreen and back into PiP and ensure that it is in the same
+ // position as before we expanded (and that the default bounds reflect that)
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_EXPAND_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForAppTransitionIdle();
+ mAmWmState.computeState(true);
+ // Due to rounding in how we save and apply the snap fraction we may be a pixel off, so just
+ // account for that in this check
+ offsetStackBounds.inset(-1, -1);
+ assertTrue("Expected offsetBounds=" + offsetStackBounds + " to contain bounds="
+ + getPinnedStackBounds(), offsetStackBounds.contains(getPinnedStackBounds()));
+
+ // Expand the PiP, then launch an activity in a new task, and ensure that the PiP goes back
+ // to the default position (and not the saved position) the next time it is launched
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_EXPAND_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
+ launchActivity(TEST_ACTIVITY);
+ executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
+ mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_RESUMED);
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForAppTransitionIdle();
+ mAmWmState.computeState(true);
+ assertTrue("Expected initialBounds=" + initialDefaultBounds + " to equal bounds="
+ + getPinnedStackBounds(), initialDefaultBounds.equals(getPinnedStackBounds()));
+ }
+
+ @Presubmit
+ @Test
+ @FlakyTest(bugId = 71792368)
+ public void testEnterPictureInPictureDiscardSavedPositionOnFinish() throws Exception {
+ if (!supportsPip()) return;
+
+ // Launch PiP activity with auto-enter PiP, save the default position of the PiP
+ // (while the PiP is still animating sleep)
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+ mAmWmState.waitForAppTransitionIdle();
+
+ // Move the PiP to a new position on screen
+ final int stackId = getPinnedStack().mStackId;
+ final Rect initialDefaultBounds = mAmWmState.getWmState().getDefaultPinnedStackBounds();
+ final Rect offsetStackBounds = getPinnedStackBounds();
+ offsetStackBounds.offset(0, -100);
+ resizeStack(stackId, offsetStackBounds.left, offsetStackBounds.top, offsetStackBounds.right,
+ offsetStackBounds.bottom);
+
+ // Finish the activity
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_FINISH);
+ mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_DESTROYED);
+ mAmWmState.waitForWithAmState((amState) -> {
+ return getPinnedStack() == null;
+ }, "Waiting for pinned stack to be removed...");
+ assertPinnedStackDoesNotExist();
+
+ // Ensure that starting the same PiP activity after it finished will go to the default
+ // bounds
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+ assertPinnedStackExists();
+ mAmWmState.waitForAppTransitionIdle();
+ mAmWmState.computeState(true);
+ assertTrue("Expected initialBounds=" + initialDefaultBounds + " to equal bounds="
+ + getPinnedStackBounds(), initialDefaultBounds.equals(getPinnedStackBounds()));
+ }
+
+ private static final Pattern sAppBoundsPattern = Pattern.compile(
+ "(.+)mAppBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)(.*)");
+
+ /** Read app bounds in last applied configuration from logs. */
+ private Rect readAppBounds(String activityName, String logSeparator) throws Exception {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ final Matcher matcher = sAppBoundsPattern.matcher(line);
+ if (matcher.matches()) {
+ final int left = Integer.parseInt(matcher.group(2));
+ final int top = Integer.parseInt(matcher.group(3));
+ final int right = Integer.parseInt(matcher.group(4));
+ final int bottom = Integer.parseInt(matcher.group(5));
+ return new Rect(left, top, right - left, bottom - top);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
+ * that the stack matching the {@param windowingMode} and {@param activityType} is focused, and
+ * checks the top and/or bottom tasks in the fullscreen stack if
+ * {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity} are set respectively.
+ */
+ private void assertPinnedStackStateOnMoveToFullscreen(String activityName, int windowingMode,
+ int activityType) throws Exception {
+ mAmWmState.waitForFocusedStack(windowingMode, activityType);
+ mAmWmState.assertFocusedStack("Wrong focused stack", windowingMode, activityType);
+ mAmWmState.waitForActivityState(activityName, STATE_STOPPED);
+ assertTrue(mAmWmState.getAmState().hasActivityState(activityName, STATE_STOPPED));
+ assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+ activityName, WINDOWING_MODE_FULLSCREEN));
+ assertPinnedStackDoesNotExist();
+ }
+
+ /**
+ * Asserts that the pinned stack bounds does not intersect with the IME bounds.
+ */
+ private void assertPinnedStackDoesNotIntersectIME() throws Exception {
+ // Ensure that the IME is visible
+ WindowManagerState wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ WindowManagerState.WindowState imeWinState = wmState.getInputMethodWindowState();
+ assertTrue(imeWinState != null);
+
+ // Ensure that the PIP movement is constrained by the display bounds intersecting the
+ // non-IME bounds
+ Rect imeContentFrame = imeWinState.getContentFrame();
+ Rect imeContentInsets = imeWinState.getGivenContentInsets();
+ Rect imeBounds = new Rect(imeContentFrame.left + imeContentInsets.left,
+ imeContentFrame.top + imeContentInsets.top,
+ imeContentFrame.right - imeContentInsets.width(),
+ imeContentFrame.bottom - imeContentInsets.height());
+ wmState.computeState();
+ Rect pipMovementBounds = wmState.getPinnedStackMomentBounds();
+ assertTrue(!Rect.intersects(pipMovementBounds, imeBounds));
+ }
+
+ /**
+ * Asserts that the pinned stack bounds is contained in the display bounds.
+ */
+ private void assertPinnedStackActivityIsInDisplayBounds(String activity) throws Exception {
+ final WindowManagerState.WindowState windowState = getWindowState(activity);
+ final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(
+ windowState.getDisplayId());
+ final Rect displayRect = display.getDisplayRect();
+ final Rect pinnedStackBounds = getPinnedStackBounds();
+ assertTrue(displayRect.contains(pinnedStackBounds));
+ }
+
+ /**
+ * Asserts that the pinned stack exists.
+ */
+ private void assertPinnedStackExists() throws Exception {
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ }
+
+ /**
+ * Asserts that the pinned stack does not exist.
+ */
+ private void assertPinnedStackDoesNotExist() throws Exception {
+ mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ }
+
+ /**
+ * Asserts that the pinned stack is the front stack.
+ */
+ private void assertPinnedStackIsOnTop() throws Exception {
+ mAmWmState.assertFrontStack("Pinned stack must always be on top.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ }
+
+ /**
+ * Asserts that the activity received exactly one of each of the callbacks when entering and
+ * exiting picture-in-picture.
+ */
+ private void assertValidPictureInPictureCallbackOrder(String activityName, String logSeparator)
+ throws Exception {
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+ logSeparator);
+
+ if (lifecycleCounts.mConfigurationChangedCount != 1) {
+ fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
+ + " onConfigurationChanged() calls, expecting 1");
+ } else if (lifecycleCounts.mPictureInPictureModeChangedCount != 1) {
+ fail(activityName + " has received " + lifecycleCounts.mPictureInPictureModeChangedCount
+ + " onPictureInPictureModeChanged() calls, expecting 1");
+ } else if (lifecycleCounts.mMultiWindowModeChangedCount != 1) {
+ fail(activityName + " has received " + lifecycleCounts.mMultiWindowModeChangedCount
+ + " onMultiWindowModeChanged() calls, expecting 1");
+ } else {
+ int lastPipLine = lifecycleCounts.mLastPictureInPictureModeChangedLineIndex;
+ int lastMwLine = lifecycleCounts.mLastMultiWindowModeChangedLineIndex;
+ int lastConfigLine = lifecycleCounts.mLastConfigurationChangedLineIndex;
+ if (!(lastPipLine < lastMwLine && lastMwLine < lastConfigLine)) {
+ fail(activityName + " has received callbacks in unexpected order. Expected:"
+ + " pip < mw < config change, but got line indices: " + lastPipLine + ", "
+ + lastMwLine + ", " + lastConfigLine + " respectively");
+ }
+ }
+ }
+
+ /**
+ * Waits until the expected picture-in-picture callbacks have been made.
+ */
+ private void waitForValidPictureInPictureCallbacks(String activityName, String logSeparator)
+ throws Exception {
+ mAmWmState.waitFor((amState, wmState) -> {
+ try {
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
+ activityName, logSeparator);
+ return lifecycleCounts.mConfigurationChangedCount == 1 &&
+ lifecycleCounts.mPictureInPictureModeChangedCount == 1 &&
+ lifecycleCounts.mMultiWindowModeChangedCount == 1;
+ } catch (Exception e) {
+ return false;
+ }
+ }, "Waiting for picture-in-picture activity callbacks...");
+ }
+
+ private void waitForValidAspectRatio(int num, int denom) throws Exception {
+ // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
+ // and before we can check the pinned stack bounds
+ mAmWmState.waitForWithAmState((state) -> {
+ Rect bounds = state.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED).getBounds();
+ return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
+ }, "waitForValidAspectRatio");
+ }
+
+ /**
+ * @return the window state for the given {@param activity}'s window.
+ */
+ private WindowManagerState.WindowState getWindowState(String activity) throws Exception {
+ String windowName = getWindowName(activity);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activity).build());
+ final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
+ mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, tempWindowList);
+ return tempWindowList.get(0);
+ }
+
+ /**
+ * @return the current pinned stack.
+ */
+ private ActivityStack getPinnedStack() {
+ return mAmWmState.getAmState().getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
+ }
+
+ /**
+ * @return the current pinned stack bounds.
+ */
+ private Rect getPinnedStackBounds() {
+ return getPinnedStack().getBounds();
+ }
+
+ /**
+ * Compares two floats with a common epsilon.
+ */
+ private void assertFloatEquals(float actual, float expected) {
+ if (!floatEquals(actual, expected)) {
+ fail(expected + " not equal to " + actual);
+ }
+ }
+
+ private boolean floatEquals(float a, float b) {
+ return Math.abs(a - b) < FLOAT_COMPARE_EPSILON;
+ }
+
+ /**
+ * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
+ */
+ private void tapToFinishPip() throws Exception {
+ Rect pinnedStackBounds = getPinnedStackBounds();
+ int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
+ int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
+ executeShellCommand(String.format("input tap %d %d", tapX, tapY));
+ }
+
+ /**
+ * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
+ */
+ private void launchPinnedActivityAsTaskOverlay(String activityName, int taskId)
+ throws Exception {
+ executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
+
+ mAmWmState.waitForValidState(activityName, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ }
+
+ /**
+ * Sets an app-ops op for a given package to a given mode.
+ */
+ private void setAppOpsOpToMode(String packageName, String op, int mode) throws Exception {
+ executeShellCommand(String.format("appops set %s %s %d", packageName, op, mode));
+ }
+
+ /**
+ * Triggers the window keycode.
+ */
+ private void pressWindowButton() throws Exception {
+ mDevice.pressKeyCode(KEYCODE_WINDOW);
+ }
+
+ /**
+ * TODO: Improve tests check to actually check that apps are not interactive instead of checking
+ * if the stack is focused.
+ */
+ private void pinnedStackTester(String startActivityCmd, String startActivity,
+ String topActivityName, boolean moveTopToPinnedStack, boolean isFocusable)
+ throws Exception {
+ executeShellCommand(startActivityCmd);
+ mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(startActivity).build());
+
+ if (moveTopToPinnedStack) {
+ final int stackId = mAmWmState.getAmState().getStackIdByActivityName(topActivityName);
+
+ assertNotEquals(stackId, INVALID_STACK_ID);
+ executeShellCommand(getMoveToPinnedStackCommand(stackId));
+ }
+
+ mAmWmState.waitForValidState(topActivityName,
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.computeState();
+
+ if (supportsPip()) {
+ final String windowName = getWindowName(topActivityName);
+ assertPinnedStackExists();
+ mAmWmState.assertFrontStack("Pinned stack must be the front stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(topActivityName, true);
+
+ if (isFocusable) {
+ mAmWmState.assertFocusedStack("Pinned stack must be the focused stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedActivity(
+ "Pinned activity must be focused activity.", topActivityName);
+ mAmWmState.assertFocusedWindow(
+ "Pinned window must be focused window.", windowName);
+ // Not checking for resumed state here because PiP overlay can be launched on top
+ // in different task by SystemUI.
+ } else {
+ // Don't assert that the stack is not focused as a focusable PiP overlay can be
+ // launched on top as a task overlay by SystemUI.
+ mAmWmState.assertNotFocusedActivity(
+ "Pinned activity can't be the focused activity.", topActivityName);
+ mAmWmState.assertNotResumedActivity(
+ "Pinned activity can't be the resumed activity.", topActivityName);
+ mAmWmState.assertNotFocusedWindow(
+ "Pinned window can't be focused window.", windowName);
+ }
+ } else {
+ mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java
new file mode 100644
index 0000000..faa72a9
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assume.assumeTrue;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerReplaceWindowTests
+ */
+public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
+
+ private static final String SLOW_CREATE_ACTIVITY_NAME = "SlowCreateActivity";
+ private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+
+ private List<String> mTempWindowTokens = new ArrayList();
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+ }
+
+ @Test
+ public void testReplaceWindow_Dock_Relaunch() throws Exception {
+ testReplaceWindow_Dock(true);
+ }
+
+ @Test
+ public void testReplaceWindow_Dock_NoRelaunch() throws Exception {
+ testReplaceWindow_Dock(false);
+ }
+
+ private void testReplaceWindow_Dock(boolean relaunch) throws Exception {
+ final String activityName =
+ relaunch ? SLOW_CREATE_ACTIVITY_NAME : NO_RELAUNCH_ACTIVITY_NAME;
+ final String windowName = getWindowName(activityName);
+ final String amStartCmd = getAmStartCmd(activityName);
+
+ executeShellCommand(amStartCmd);
+
+ // Sleep 2 seconds, then check if the window is started properly.
+ // SlowCreateActivity will do a sleep inside its onCreate() to simulate a
+ // slow-starting app. So instead of relying on WindowManagerState's
+ // retrying mechanism, we do an explicit sleep to avoid excess spews
+ // from WindowManagerState.
+ if (SLOW_CREATE_ACTIVITY_NAME.equals(activityName)) {
+ Thread.sleep(2000);
+ }
+
+ log("==========Before Docking========");
+ final String oldToken = getWindowToken(windowName, activityName);
+
+ // Move to docked stack
+ setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+ // Sleep 5 seconds, then check if the window is replaced properly.
+ Thread.sleep(5000);
+
+ log("==========After Docking========");
+ final String newToken = getWindowToken(windowName, activityName);
+
+ // For both relaunch and not relaunch case, we'd like the window to be kept.
+ Assert.assertEquals("Window replaced while docking.", oldToken, newToken);
+ }
+
+ private String getWindowToken(String windowName, String activityName)
+ throws Exception {
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+
+ mAmWmState.assertVisibility(activityName, true);
+
+ mAmWmState.getWmState().getMatchingWindowTokens(windowName, mTempWindowTokens);
+
+ Assert.assertEquals("Should have exactly one window for the activity.",
+ 1, mTempWindowTokens.size());
+
+ return mTempWindowTokens.get(0);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
new file mode 100644
index 0000000..8c793b9
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import android.support.test.filters.FlakyTest;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerSplitScreenTests
+ */
+public class ActivityManagerSplitScreenTests extends ActivityManagerTestBase {
+
+ private static final String TEST_ACTIVITY_NAME = "TestActivity";
+ private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
+ private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
+ private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+ private static final String SINGLE_INSTANCE_ACTIVITY_NAME = "SingleInstanceActivity";
+ private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
+
+ private static final String TEST_ACTIVITY_ACTION_FINISH =
+ "android.server.am.TestActivity.finish_self";
+
+ private static final int TASK_SIZE = 600;
+ private static final int STACK_SIZE = 300;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue("Skipping test: no split multi-window support",
+ supportsSplitScreenMultiWindow());
+ }
+
+ @Test
+ public void testMinimumDeviceSize() throws Exception {
+ mAmWmState.assertDeviceDefaultDisplaySize(
+ "Devices supporting multi-window must be larger than the default minimum"
+ + " task size");
+ }
+
+ @Test
+ @Presubmit
+ public void testStackList() throws Exception {
+ launchActivity(TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] {TEST_ACTIVITY_NAME});
+ mAmWmState.assertContainsStack("Must contain home stack.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertDoesNotContainStack("Must not contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ @Presubmit
+ public void testDockActivity() throws Exception {
+ launchActivityInSplitScreenWithRecents(TEST_ACTIVITY_NAME);
+ mAmWmState.assertContainsStack("Must contain home stack.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ @Presubmit
+ public void testNonResizeableNotDocked() throws Exception {
+ launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY_NAME);
+
+ mAmWmState.assertContainsStack("Must contain home stack.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ mAmWmState.assertDoesNotContainStack("Must not contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFrontStack("Fullscreen stack must be front stack.",
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ @Presubmit
+ public void testLaunchToSide() throws Exception {
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ @Presubmit
+ public void testLaunchToSideMultiWindowCallbacks() throws Exception {
+ // Launch two activities in split-screen mode.
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+ // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
+ final String logSeparator = clearLogcat();
+ removeStacksInWindowingModes(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+ TEST_ACTIVITY_NAME, logSeparator);
+ assertEquals(1, lifecycleCounts.mMultiWindowModeChangedCount);
+ }
+
+ @Test
+ @Presubmit
+ public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+
+ // Move to docked stack.
+ String logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+ TEST_ACTIVITY_NAME, logSeparator);
+ assertEquals("mMultiWindowModeChangedCount",
+ 1, lifecycleCounts.mMultiWindowModeChangedCount);
+ assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+
+ // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+ // will come up.
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+ // Move activity back to fullscreen stack.
+ logSeparator = clearLogcat();
+ setActivityTaskWindowingMode(TEST_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+ lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY_NAME, logSeparator);
+ assertEquals("mMultiWindowModeChangedCount",
+ 1, lifecycleCounts.mMultiWindowModeChangedCount);
+ assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+ }
+
+ @Test
+ @Presubmit
+ public void testLaunchToSideAndBringToFront() throws Exception {
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+
+ int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ TEST_ACTIVITY_NAME);
+
+ // Launch another activity to side to cover first one.
+ launchActivity(
+ NO_RELAUNCH_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ int taskNumberCovered = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertEquals("Fullscreen stack must have one task added.",
+ taskNumberInitial + 1, taskNumberCovered);
+ mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+ NO_RELAUNCH_ACTIVITY_NAME);
+
+ // Launch activity that was first launched to side. It should be brought to front.
+ getLaunchActivityBuilder()
+ .setTargetActivityName(TEST_ACTIVITY_NAME)
+ .setToSide(true)
+ .setWaitForLaunched(true)
+ .execute();
+ int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertEquals("Task number in fullscreen stack must remain the same.",
+ taskNumberCovered, taskNumberFinal);
+ mAmWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+ TEST_ACTIVITY_NAME);
+ }
+
+ @Test
+ @Presubmit
+ public void testLaunchToSideMultiple() throws Exception {
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+
+ int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertNotNull("Launched to side activity must be in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+ // Try to launch to side same activity again.
+ getLaunchActivityBuilder().setToSide(true).execute();
+ final String[] waitForActivitiesVisible =
+ new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
+ mAmWmState.computeState(waitForActivitiesVisible);
+ int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
+ mAmWmState.assertFocusedActivity("Launched to side activity must remain in front.",
+ TEST_ACTIVITY_NAME);
+ assertNotNull("Launched to side activity must remain in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+ }
+
+ @Test
+ @Presubmit
+ public void testLaunchToSideSingleInstance() throws Exception {
+ launchTargetToSide(SINGLE_INSTANCE_ACTIVITY_NAME, false);
+ }
+
+ @Test
+ public void testLaunchToSideSingleTask() throws Exception {
+ launchTargetToSide(SINGLE_TASK_ACTIVITY_NAME, false);
+ }
+
+ @FlakyTest
+ @Presubmit
+ @Test
+ public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
+ launchTargetToSide(TEST_ACTIVITY_NAME, true);
+ }
+
+ private void launchTargetToSide(String targetActivityName, boolean taskCountMustIncrement)
+ throws Exception {
+ final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
+ .setTargetActivityName(targetActivityName)
+ .setToSide(true)
+ .setRandomData(true)
+ .setMultipleTask(false);
+
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+ targetActivityLauncher);
+
+ final WaitForValidActivityState[] waitForActivitiesVisible =
+ new WaitForValidActivityState[] {
+ new WaitForValidActivityState.Builder(targetActivityName).build(),
+ new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build()
+ };
+
+ mAmWmState.computeState(waitForActivitiesVisible);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertNotNull("Launched to side activity must be in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+ // Try to launch to side same activity again with different data.
+ targetActivityLauncher.execute();
+ mAmWmState.computeState(waitForActivitiesVisible);
+ int taskNumberSecondLaunch = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ if (taskCountMustIncrement) {
+ assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+ taskNumberSecondLaunch);
+ } else {
+ assertEquals("Task number must not change.", taskNumberInitial,
+ taskNumberSecondLaunch);
+ }
+ mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ targetActivityName);
+ assertNotNull("Launched to side activity must be launched in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+ // Try to launch to side same activity again with no data.
+ targetActivityLauncher.setRandomData(false).execute();
+ mAmWmState.computeState(waitForActivitiesVisible);
+ int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ if (taskCountMustIncrement) {
+ assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
+ taskNumberFinal);
+ } else {
+ assertEquals("Task number must not change.", taskNumberSecondLaunch,
+ taskNumberFinal);
+ }
+ mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ targetActivityName);
+ assertNotNull("Launched to side activity must be launched in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+ }
+
+ @Presubmit
+ @Test
+ public void testLaunchToSideMultipleWithFlag() throws Exception {
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+ int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertNotNull("Launched to side activity must be in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+ // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
+ getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
+ final String[] waitForActivitiesVisible =
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+ mAmWmState.computeState(waitForActivitiesVisible);
+ int taskNumberFinal = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+ taskNumberFinal);
+ mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+ TEST_ACTIVITY_NAME);
+ assertNotNull("Launched to side activity must remain in fullscreen stack.",
+ mAmWmState.getAmState().getTaskByActivityName(
+ TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+ }
+
+ @Test
+ public void testRotationWhenDocked() throws Exception {
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+ // Rotate device single steps (90°) 0-1-2-3.
+ // Each time we compute the state we implicitly assert valid bounds.
+ String[] waitForActivitiesVisible =
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+ try (final RotationSession rotationSession = new RotationSession()) {
+ for (int i = 0; i < 4; i++) {
+ rotationSession.set(i);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+ // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
+ // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
+ rotationSession.set(ROTATION_90);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_270);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_180);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+ }
+
+ @Test
+ @Presubmit
+ public void testRotationWhenDockedWhileLocked() throws Exception {
+ launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+ mAmWmState.assertSanity();
+ mAmWmState.assertContainsStack("Must contain fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+ String[] waitForActivitiesVisible =
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+ try (final RotationSession rotationSession = new RotationSession()) {
+ for (int i = 0; i < 4; i++) {
+ sleepDevice();
+ rotationSession.set(i);
+ wakeUpAndUnlockDevice();
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+ }
+ }
+
+ @Test
+ public void testMinimizedFromEachDockedSide() throws Exception {
+ try (final RotationSession rotationSession = new RotationSession()) {
+ for (int i = 0; i < 2; i++) {
+ rotationSession.set(i);
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+ if (!mAmWmState.isScreenPortrait() && isTablet()) {
+ // Test minimize to the right only on tablets in landscape
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME,
+ SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+ }
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ }
+ }
+ }
+
+ @Test
+ @Presubmit
+ public void testRotationWhileDockMinimized() throws Exception {
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+
+ // Rotate device single steps (90°) 0-1-2-3.
+ // Each time we compute the state we implicitly assert valid bounds in minimized mode.
+ String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
+ try (final RotationSession rotationSession = new RotationSession()) {
+ for (int i = 0; i < 4; i++) {
+ rotationSession.set(i);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+
+ // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
+ // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side in
+ // minimized mode.
+ rotationSession.set(ROTATION_90);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_270);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_180);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ rotationSession.set(ROTATION_0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+ }
+
+ @Test
+ public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
+ // Rotate the screen to check that minimize, unminimize, dismiss the docked stack and then
+ // going home has the correct app transition
+ try (final RotationSession rotationSession = new RotationSession()) {
+ for (int i = 0; i < 4; i++) {
+ rotationSession.set(i);
+ launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY_NAME);
+
+ // Unminimize the docked stack
+ pressAppSwitchButton();
+ waitForDockNotMinimized();
+ assertDockNotMinimized();
+
+ // Dismiss the dock stack
+ launchActivity(TEST_ACTIVITY_NAME,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ setActivityTaskWindowingMode(DOCKED_ACTIVITY_NAME,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.computeState(new String[]{DOCKED_ACTIVITY_NAME});
+
+ // Go home and check the app transition
+ assertNotSame(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+ pressHomeButton();
+ mAmWmState.computeState();
+ assertEquals(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+ }
+ }
+ }
+
+ @Test
+ @Presubmit
+ public void testFinishDockActivityWhileMinimized() throws Exception {
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+
+ executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
+ waitForDockNotMinimized();
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
+ assertDockNotMinimized();
+ }
+
+ @Test
+ @Presubmit
+ public void testDockedStackToMinimizeWhenUnlocked() throws Exception {
+ launchActivityInSplitScreenWithRecents(TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ sleepDevice();
+ wakeUpAndUnlockDevice();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ assertDockMinimized();
+ }
+
+ @Test
+ public void testMinimizedStateWhenUnlockedAndUnMinimized() throws Exception {
+ launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+
+ sleepDevice();
+ wakeUpAndUnlockDevice();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+ // Unminimized back to splitscreen
+ pressAppSwitchButton();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ }
+
+ @Test
+ @Presubmit
+ public void testResizeDockedStack() throws Exception {
+ launchActivitiesInSplitScreen(DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME);
+ resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
+ mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(DOCKED_ACTIVITY_NAME).build());
+ mAmWmState.assertContainsStack("Must contain secondary split-screen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain primary split-screen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ assertEquals(new Rect(0, 0, STACK_SIZE, STACK_SIZE),
+ mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).getBounds());
+ mAmWmState.assertDockedTaskBounds(TASK_SIZE, TASK_SIZE, DOCKED_ACTIVITY_NAME);
+ mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+ }
+
+ @Test
+ public void testActivityLifeCycleOnResizeDockedStack() throws Exception {
+ final WaitForValidActivityState[] waitTestActivityName =
+ new WaitForValidActivityState[] {new WaitForValidActivityState.Builder(
+ TEST_ACTIVITY_NAME).build()};
+ launchActivity(TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(waitTestActivityName);
+ final Rect fullScreenBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).getBounds();
+
+ setActivityTaskWindowingMode(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mAmWmState.computeState(waitTestActivityName);
+ launchActivity(NO_RELAUNCH_ACTIVITY_NAME,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY_NAME).build());
+ final Rect initialDockBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
+
+ final String logSeparator = clearLogcat();
+
+ Rect newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, true);
+ resizeDockedStack(newBounds.width(), newBounds.height(), newBounds.width(), newBounds.height());
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY_NAME).build());
+
+ // We resize twice to make sure we cross an orientation change threshold for both
+ // activities.
+ newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, false);
+ resizeDockedStack(newBounds.width(), newBounds.height(), newBounds.width(), newBounds.height());
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY_NAME).build());
+ assertActivityLifecycle(TEST_ACTIVITY_NAME, true /* relaunched */, logSeparator);
+ assertActivityLifecycle(NO_RELAUNCH_ACTIVITY_NAME, false /* relaunched */, logSeparator);
+ }
+
+ private Rect computeNewDockBounds(
+ Rect fullscreenBounds, Rect dockBounds, boolean reduceSize) {
+ final boolean inLandscape = fullscreenBounds.width() > dockBounds.width();
+ // We are either increasing size or reducing it.
+ final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
+ final Rect newBounds = new Rect(dockBounds);
+ if (inLandscape) {
+ // In landscape we change the width.
+ newBounds.right = (int) (newBounds.left + (newBounds.width() * sizeChangeFactor));
+ } else {
+ // In portrait we change the height
+ newBounds.bottom = (int) (newBounds.top + (newBounds.height() * sizeChangeFactor));
+ }
+
+ return newBounds;
+ }
+
+ @Test
+ @Presubmit
+ public void testStackListOrderLaunchDockedActivity() throws Exception {
+ launchActivityInSplitScreenWithRecents(TEST_ACTIVITY_NAME);
+
+ final int homeStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+ final int recentsStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
+ assertTrue("Recents stack should be on top of home stack",
+ recentsStackIndex < homeStackIndex);
+ }
+
+ @Test
+ @Presubmit
+ public void testStackListOrderOnSplitScreenDismissed() throws Exception {
+ launchActivitiesInSplitScreen(DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME);
+
+ setActivityTaskWindowingMode(DOCKED_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(
+ DOCKED_ACTIVITY_NAME).setWindowingMode(WINDOWING_MODE_FULLSCREEN).build());
+
+ final int homeStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+ final int prevSplitScreenPrimaryIndex =
+ mAmWmState.getAmState().getStackIndexByActivityName(DOCKED_ACTIVITY_NAME);
+ final int prevSplitScreenSecondaryIndex =
+ mAmWmState.getAmState().getStackIndexByActivityName(TEST_ACTIVITY_NAME);
+
+ final int expectedHomeStackIndex =
+ (prevSplitScreenPrimaryIndex > prevSplitScreenSecondaryIndex
+ ? prevSplitScreenPrimaryIndex : prevSplitScreenSecondaryIndex) - 1;
+ assertTrue("Home stack needs to be directly behind the top stack",
+ expectedHomeStackIndex == homeStackIndex);
+ }
+
+ private void launchActivityInDockStackAndMinimize(String activityName) throws Exception {
+ launchActivityInDockStackAndMinimize(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+ }
+
+ private void launchActivityInDockStackAndMinimize(String activityName, int createMode)
+ throws Exception {
+ launchActivityInSplitScreenWithRecents(activityName, createMode);
+ pressHomeButton();
+ waitForAndAssertDockMinimized();
+ }
+
+ private void assertDockMinimized() {
+ assertTrue(mAmWmState.getWmState().isDockedStackMinimized());
+ }
+
+ private void waitForAndAssertDockMinimized() throws Exception {
+ waitForDockMinimized();
+ assertDockMinimized();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ mAmWmState.assertContainsStack("Must contain docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ private void assertDockNotMinimized() {
+ assertFalse(mAmWmState.getWmState().isDockedStackMinimized());
+ }
+
+ private void waitForDockMinimized() throws Exception {
+ mAmWmState.waitForWithWmState(state -> state.isDockedStackMinimized(),
+ "***Waiting for Dock stack to be minimized");
+ }
+
+ private void waitForDockNotMinimized() throws Exception {
+ mAmWmState.waitForWithWmState(state -> !state.isDockedStackMinimized(),
+ "***Waiting for Dock stack to not be minimized");
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
new file mode 100644
index 0000000..6f22893
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.server.am.WindowManagerState.TRANSIT_ACTIVITY_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_TASK_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_TASK_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_INTRA_CLOSE;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_INTRA_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+/**
+ * This test tests the transition type selection logic in ActivityManager/WindowManager.
+ * BottomActivity is started first, then TopActivity, and we check the transition type that the
+ * system selects when TopActivity enters or exits under various setups.
+ *
+ * Note that we only require the correct transition type to be reported (eg. TRANSIT_ACTIVITY_OPEN,
+ * TRANSIT_TASK_CLOSE, TRANSIT_WALLPAPER_OPEN, etc.). The exact animation is unspecified and can be
+ * overridden.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerTransitionSelectionTests
+ */
+@Presubmit
+public class ActivityManagerTransitionSelectionTests extends ActivityManagerTestBase {
+
+ private static final String BOTTOM_ACTIVITY_NAME = "BottomActivity";
+ private static final String TOP_ACTIVITY_NAME = "TopActivity";
+ private static final String TRANSLUCENT_TOP_ACTIVITY_NAME = "TranslucentTopActivity";
+
+ //------------------------------------------------------------------------//
+
+ // Test activity open/close under normal timing
+ @Test
+ public void testOpenActivity_NeitherWallpaper() throws Exception {
+ testOpenActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_ACTIVITY_OPEN);
+ }
+
+ @Test
+ @FlakyTest(bugId = 71792333)
+ public void testCloseActivity_NeitherWallpaper() throws Exception {
+ testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
+ }
+
+ @Test
+ public void testOpenActivity_BottomWallpaper() throws Exception {
+ testOpenActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
+ }
+
+ @Test
+ public void testCloseActivity_BottomWallpaper() throws Exception {
+ testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+ }
+
+ @Test
+ public void testOpenActivity_BothWallpaper() throws Exception {
+ testOpenActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
+ }
+
+ @Test
+ public void testCloseActivity_BothWallpaper() throws Exception {
+ testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+ }
+
+ //------------------------------------------------------------------------//
+
+ // Test task open/close under normal timing
+ @Test
+ public void testOpenTask_NeitherWallpaper() throws Exception {
+ testOpenTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_TASK_OPEN);
+ }
+
+ @Test
+ public void testCloseTask_NeitherWallpaper() throws Exception {
+ testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_TASK_CLOSE);
+ }
+
+ @Test
+ public void testOpenTask_BottomWallpaper() throws Exception {
+ testOpenTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_CLOSE);
+ }
+
+ @Test
+ public void testCloseTask_BottomWallpaper() throws Exception {
+ testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+ }
+
+ @Test
+ public void testOpenTask_BothWallpaper() throws Exception {
+ testOpenTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_OPEN);
+ }
+
+ @Test
+ public void testCloseTask_BothWallpaper() throws Exception {
+ testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ false /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+ }
+
+ //------------------------------------------------------------------------//
+
+ // Test activity close -- bottom activity slow in stopping
+ // These simulate the case where the bottom activity is resumed
+ // before AM receives its activitiyStopped
+ @Test
+ @FlakyTest(bugId = 71792333)
+ public void testCloseActivity_NeitherWallpaper_SlowStop() throws Exception {
+ testCloseActivity(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ true /*slowStop*/, TRANSIT_ACTIVITY_CLOSE);
+ }
+
+ @Test
+ public void testCloseActivity_BottomWallpaper_SlowStop() throws Exception {
+ testCloseActivity(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+ }
+
+ @Test
+ public void testCloseActivity_BothWallpaper_SlowStop() throws Exception {
+ testCloseActivity(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+ }
+
+ //------------------------------------------------------------------------//
+
+ // Test task close -- bottom task top activity slow in stopping
+ // These simulate the case where the bottom activity is resumed
+ // before AM receives its activitiyStopped
+ @Test
+ public void testCloseTask_NeitherWallpaper_SlowStop() throws Exception {
+ testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ true /*slowStop*/, TRANSIT_TASK_CLOSE);
+ }
+
+ @Test
+ public void testCloseTask_BottomWallpaper_SlowStop() throws Exception {
+ testCloseTask(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ true /*slowStop*/, TRANSIT_WALLPAPER_OPEN);
+ }
+
+ @Test
+ public void testCloseTask_BothWallpaper_SlowStop() throws Exception {
+ testCloseTask(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ true /*slowStop*/, TRANSIT_WALLPAPER_INTRA_CLOSE);
+ }
+
+ //------------------------------------------------------------------------//
+
+ /// Test closing of translucent activity/task
+ @Test
+ public void testCloseActivity_NeitherWallpaper_Translucent() throws Exception {
+ testCloseActivityTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ TRANSIT_ACTIVITY_CLOSE);
+ }
+
+ @Test
+ @FlakyTest(bugId = 71792333)
+ public void testCloseActivity_BottomWallpaper_Translucent() throws Exception {
+ testCloseActivityTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ TRANSIT_WALLPAPER_OPEN);
+ }
+
+ @Test
+ public void testCloseActivity_BothWallpaper_Translucent() throws Exception {
+ testCloseActivityTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ TRANSIT_WALLPAPER_INTRA_CLOSE);
+ }
+
+ @Test
+ public void testCloseTask_NeitherWallpaper_Translucent() throws Exception {
+ testCloseTaskTranslucent(false /*bottomWallpaper*/, false /*topWallpaper*/,
+ TRANSIT_TASK_CLOSE);
+ }
+
+ @Test
+ public void testCloseTask_BottomWallpaper_Translucent() throws Exception {
+ testCloseTaskTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
+ TRANSIT_WALLPAPER_OPEN);
+ }
+
+ @Test
+ public void testCloseTask_BothWallpaper_Translucent() throws Exception {
+ testCloseTaskTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
+ TRANSIT_WALLPAPER_INTRA_CLOSE);
+ }
+
+ //------------------------------------------------------------------------//
+
+ private void testOpenActivity(boolean bottomWallpaper,
+ boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+ testTransitionSelection(true /*testOpen*/, false /*testNewTask*/,
+ bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+ }
+ private void testCloseActivity(boolean bottomWallpaper,
+ boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+ testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
+ bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+ }
+ private void testOpenTask(boolean bottomWallpaper,
+ boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+ testTransitionSelection(true /*testOpen*/, true /*testNewTask*/,
+ bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+ }
+ private void testCloseTask(boolean bottomWallpaper,
+ boolean topWallpaper, boolean slowStop, String expectedTransit) throws Exception {
+ testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
+ bottomWallpaper, topWallpaper, false /*topTranslucent*/, slowStop, expectedTransit);
+ }
+ private void testCloseActivityTranslucent(boolean bottomWallpaper,
+ boolean topWallpaper, String expectedTransit) throws Exception {
+ testTransitionSelection(false /*testOpen*/, false /*testNewTask*/,
+ bottomWallpaper, topWallpaper, true /*topTranslucent*/,
+ false /*slowStop*/, expectedTransit);
+ }
+ private void testCloseTaskTranslucent(boolean bottomWallpaper,
+ boolean topWallpaper, String expectedTransit) throws Exception {
+ testTransitionSelection(false /*testOpen*/, true /*testNewTask*/,
+ bottomWallpaper, topWallpaper, true /*topTranslucent*/,
+ false /*slowStop*/, expectedTransit);
+ }
+ //------------------------------------------------------------------------//
+
+ private void testTransitionSelection(
+ boolean testOpen, boolean testNewTask,
+ boolean bottomWallpaper, boolean topWallpaper, boolean topTranslucent,
+ boolean testSlowStop, String expectedTransit) throws Exception {
+ String bottomStartCmd = getAmStartCmd(BOTTOM_ACTIVITY_NAME);
+ if (bottomWallpaper) {
+ bottomStartCmd += " --ez USE_WALLPAPER true";
+ }
+ if (testSlowStop) {
+ bottomStartCmd += " --ei STOP_DELAY 3000";
+ }
+ executeShellCommand(bottomStartCmd);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(BOTTOM_ACTIVITY_NAME).build());
+
+ final String topActivityName = topTranslucent ?
+ TRANSLUCENT_TOP_ACTIVITY_NAME : TOP_ACTIVITY_NAME;
+ String topStartCmd = getAmStartCmd(topActivityName);
+ if (testNewTask) {
+ topStartCmd += " -f 0x18000000";
+ }
+ if (topWallpaper) {
+ topStartCmd += " --ez USE_WALLPAPER true";
+ }
+ if (!testOpen) {
+ topStartCmd += " --ei FINISH_DELAY 1000";
+ }
+ executeShellCommand(topStartCmd);
+
+ Thread.sleep(5000);
+ if (testOpen) {
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(topActivityName).build());
+ } else {
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(BOTTOM_ACTIVITY_NAME).build());
+ }
+
+ assertEquals("Picked wrong transition", expectedTransit,
+ mAmWmState.getWmState().getLastTransition());
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
new file mode 100644
index 0000000..3c0a584
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityManagerVrDisplayTests
+ */
+public class ActivityManagerVrDisplayTests extends ActivityManagerDisplayTestBase {
+ private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
+ private static final String VR_TEST_ACTIVITY_NAME = "VrTestActivity";
+ private static final int VR_VIRTUAL_DISPLAY_WIDTH = 700;
+ private static final int VR_VIRTUAL_DISPLAY_HEIGHT = 900;
+ private static final int VR_VIRTUAL_DISPLAY_DPI = 320;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(supportsVrMode());
+ }
+
+ private static class VrModeSession implements AutoCloseable {
+
+ void enablePersistentVrMode() throws Exception {
+ executeShellCommand("setprop vr_virtualdisplay true");
+ executeShellCommand("vr set-persistent-vr-mode-enabled true");
+ }
+
+ @Override
+ public void close() throws Exception {
+ executeShellCommand("vr set-persistent-vr-mode-enabled false");
+ executeShellCommand("setprop vr_virtualdisplay false");
+ }
+ }
+
+ /**
+ * Tests that any new activity launch in Vr mode is in Vr display.
+ */
+ @Test
+ public void testVrActivityLaunch() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ try (final VrModeSession vrModeSession = new VrModeSession()) {
+ // Put the device in persistent vr mode.
+ vrModeSession.enablePersistentVrMode();
+
+ // Launch the VR activity.
+ launchActivity(VR_TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState(VR_TEST_ACTIVITY_NAME));
+ mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
+
+ // Launch the non-VR 2D activity and check where it ends up.
+ launchActivity(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+
+ // Ensure that the subsequent activity is visible
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ LAUNCHING_ACTIVITY);
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Launched activity must be resumed in focused stack",
+ getActivityComponentName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
+
+ // Check if the launch activity is in Vr virtual display id.
+ final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+ final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+ VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
+ assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+ // Check if the focused activity is on this virtual stack.
+ assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+ focusedStack.mDisplayId);
+ }
+ }
+
+ /**
+ * Tests that any activity already present is re-launched in Vr display in vr mode.
+ */
+ @Test
+ public void testVrActivityReLaunch() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ // Launch a 2D activity.
+ launchActivity(LAUNCHING_ACTIVITY);
+
+ try (final VrModeSession vrModeSession = new VrModeSession()) {
+ // Put the device in persistent vr mode.
+ vrModeSession.enablePersistentVrMode();
+
+ // Launch the VR activity.
+ launchActivity(VR_TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState(VR_TEST_ACTIVITY_NAME));
+ mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
+
+ // Re-launch the non-VR 2D activity and check where it ends up.
+ launchActivity(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState(LAUNCHING_ACTIVITY));
+
+ // Ensure that the subsequent activity is visible
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ LAUNCHING_ACTIVITY);
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Launched activity must be resumed in focused stack",
+ getActivityComponentName(LAUNCHING_ACTIVITY), focusedStack.mResumedActivity);
+
+ // Check if the launch activity is in Vr virtual display id.
+ final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+ final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+ VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT, VR_VIRTUAL_DISPLAY_DPI);
+ assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+ // Check if the focused activity is on this virtual stack.
+ assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+ focusedStack.mDisplayId);
+ }
+ }
+
+ /**
+ * Tests that any new activity launch post Vr mode is in the main display.
+ */
+ @Test
+ public void testActivityLaunchPostVr() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ try (final VrModeSession vrModeSession = new VrModeSession()) {
+ // Put the device in persistent vr mode.
+ vrModeSession.enablePersistentVrMode();
+
+ // Launch the VR activity.
+ launchActivity(VR_TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState(VR_TEST_ACTIVITY_NAME));
+ mAmWmState.assertVisibility(VR_TEST_ACTIVITY_NAME, true /* visible */);
+
+ // Launch the non-VR 2D activity and check where it ends up.
+ launchActivity(ALT_LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState(ALT_LAUNCHING_ACTIVITY));
+
+ // Ensure that the subsequent activity is visible
+ mAmWmState.assertVisibility(ALT_LAUNCHING_ACTIVITY, true /* visible */);
+
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ ALT_LAUNCHING_ACTIVITY);
+ final int focusedStackId = mAmWmState.getAmState().getFocusedStackId();
+ final ActivityManagerState.ActivityStack focusedStack
+ = mAmWmState.getAmState().getStackById(focusedStackId);
+ assertEquals("Launched activity must be resumed in focused stack",
+ getActivityComponentName(ALT_LAUNCHING_ACTIVITY),
+ focusedStack.mResumedActivity);
+
+ // Check if the launch activity is in Vr virtual display id.
+ final List<ActivityDisplay> reportedDisplays = getDisplaysStates();
+ final ActivityDisplay vrDisplay = getDisplayState(reportedDisplays,
+ VR_VIRTUAL_DISPLAY_WIDTH, VR_VIRTUAL_DISPLAY_HEIGHT,
+ VR_VIRTUAL_DISPLAY_DPI);
+ assertNotNull("Vr mode should have a virtual display", vrDisplay);
+
+ // Check if the focused activity is on this virtual stack.
+ assertEquals("Launch in Vr mode should be in virtual stack", vrDisplay.mId,
+ focusedStack.mDisplayId);
+
+ }
+
+ // There isn't a direct launch of activity which can take an user out of persistent VR mode.
+ // This sleep is to account for that delay and let device settle once it comes out of VR
+ // mode.
+ try {
+ Thread.sleep(2000);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Launch the non-VR 2D activity and check where it ends up.
+ launchActivity(RESIZEABLE_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState(RESIZEABLE_ACTIVITY_NAME));
+
+ // Ensure that the subsequent activity is visible
+ mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, true /* visible */);
+
+ // Check that activity is launched in focused stack on primary display.
+ mAmWmState.assertFocusedActivity("Launched activity must be focused",
+ RESIZEABLE_ACTIVITY_NAME);
+ final int frontStackId = mAmWmState.getAmState().getFrontStackId(DEFAULT_DISPLAY_ID);
+ final ActivityManagerState.ActivityStack frontStack
+ = mAmWmState.getAmState().getStackById(frontStackId);
+ assertEquals("Launched activity must be resumed in front stack",
+ getActivityComponentName(RESIZEABLE_ACTIVITY_NAME), frontStack.mResumedActivity);
+ assertEquals("Front stack must be on primary display",
+ DEFAULT_DISPLAY_ID, frontStack.mDisplayId);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
new file mode 100644
index 0000000..fd8ed2f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:AnimationBackgroundTests
+ */
+public class AnimationBackgroundTests extends ActivityManagerTestBase {
+
+ @Test
+ public void testAnimationBackground_duringAnimation() throws Exception {
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY_ID);
+ getLaunchActivityBuilder()
+ .setTargetActivityName("AnimationTestActivity")
+ .setWaitForLaunched(false)
+ .execute();
+
+ // Make sure we are in the middle of the animation.
+ mAmWmState.waitForWithWmState(state -> state
+ .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .isWindowAnimationBackgroundSurfaceShowing(),
+ "***Waiting for animation background showing");
+ assertTrue("window animation background needs to be showing", mAmWmState.getWmState()
+ .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .isWindowAnimationBackgroundSurfaceShowing());
+ }
+
+ @Test
+ public void testAnimationBackground_gone() throws Exception {
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY_ID);
+ getLaunchActivityBuilder().setTargetActivityName("AnimationTestActivity").execute();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("AnimationTestActivity").build());
+ assertFalse("window animation background needs to be gone", mAmWmState.getWmState()
+ .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .isWindowAnimationBackgroundSurfaceShowing());
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
new file mode 100644
index 0000000..0cb300a
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.content.Context.WINDOW_SERVICE;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:AspectRatioTests
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AspectRatioTests extends AspectRatioTestsBase {
+
+ // The max. aspect ratio the test activities are using.
+ private static final float MAX_ASPECT_RATIO = 1.0f;
+
+ // The minimum supported device aspect ratio.
+ private static final float MIN_DEVICE_ASPECT_RATIO = 1.333f;
+
+ // The minimum supported device aspect ratio for watches.
+ private static final float MIN_WATCH_DEVICE_ASPECT_RATIO = 1.0f;
+
+ // Test target activity that has maxAspectRatio="true" and resizeableActivity="false".
+ public static class MaxAspectRatioActivity extends Activity {
+ }
+
+ // Test target activity that has maxAspectRatio="1.0" and resizeableActivity="true".
+ public static class MaxAspectRatioResizeableActivity extends Activity {
+ }
+
+ // Test target activity that has no maxAspectRatio defined and resizeableActivity="false".
+ public static class MaxAspectRatioUnsetActivity extends Activity {
+ }
+
+ // Test target activity that has maxAspectRatio defined as
+ // <meta-data android:name="android.max_aspect" android:value="1.0" />
+ // and resizeableActivity="false".
+ public static class MetaDataMaxAspectRatioActivity extends Activity {
+ }
+
+ @Rule
+ public ActivityTestRule<MaxAspectRatioActivity> mMaxAspectRatioActivity =
+ new ActivityTestRule<>(MaxAspectRatioActivity.class,
+ false /* initialTouchMode */, false /* launchActivity */);
+
+ @Rule
+ public ActivityTestRule<MaxAspectRatioResizeableActivity> mMaxAspectRatioResizeableActivity =
+ new ActivityTestRule<>(MaxAspectRatioResizeableActivity.class,
+ false /* initialTouchMode */, false /* launchActivity */);
+
+ @Rule
+ public ActivityTestRule<MetaDataMaxAspectRatioActivity> mMetaDataMaxAspectRatioActivity =
+ new ActivityTestRule<>(MetaDataMaxAspectRatioActivity.class,
+ false /* initialTouchMode */, false /* launchActivity */);
+
+ @Rule
+ public ActivityTestRule<MaxAspectRatioUnsetActivity> mMaxAspectRatioUnsetActivity =
+ new ActivityTestRule<>(MaxAspectRatioUnsetActivity.class,
+ false /* initialTouchMode */, false /* launchActivity */);
+
+ @Test
+ public void testDeviceAspectRatio() throws Exception {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
+ final Display display = wm.getDefaultDisplay();
+ final DisplayMetrics metrics = new DisplayMetrics();
+ display.getRealMetrics(metrics);
+
+ float longSide = Math.max(metrics.widthPixels, metrics.heightPixels);
+ float shortSide = Math.min(metrics.widthPixels, metrics.heightPixels);
+ float deviceAspectRatio = longSide / shortSide;
+ float expectedMinAspectRatio = context.getPackageManager().hasSystemFeature(FEATURE_WATCH)
+ ? MIN_WATCH_DEVICE_ASPECT_RATIO : MIN_DEVICE_ASPECT_RATIO;
+
+ if (deviceAspectRatio < expectedMinAspectRatio) {
+ fail("deviceAspectRatio=" + deviceAspectRatio
+ + " is less than expectedMinAspectRatio=" + expectedMinAspectRatio);
+ }
+ }
+
+ @Test
+ public void testMaxAspectRatio() throws Exception {
+ runAspectRatioTest(mMaxAspectRatioActivity, actual -> {
+ if (MAX_ASPECT_RATIO >= actual) return;
+ fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
+ });
+ }
+
+ @Test
+ public void testMetaDataMaxAspectRatio() throws Exception {
+ runAspectRatioTest(mMetaDataMaxAspectRatioActivity, actual -> {
+ if (MAX_ASPECT_RATIO >= actual) return;
+ fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
+ });
+ }
+
+ @Test
+ public void testMaxAspectRatioResizeableActivity() throws Exception {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final float expected = getAspectRatio(context);
+ final Activity testActivity = launchActivity(mMaxAspectRatioResizeableActivity);
+ PollingCheck.waitFor(testActivity::hasWindowFocus);
+
+ Display testDisplay = testActivity.findViewById(android.R.id.content).getDisplay();
+
+ // TODO(b/69982434): Fix DisplayManager NPE when getting display from Instrumentation
+ // context, then can use DisplayManager to get the aspect ratio of the correct display.
+ if (testDisplay.getDisplayId() != Display.DEFAULT_DISPLAY) {
+ return;
+ }
+
+ // Since this activity is resizeable, its aspect ratio shouldn't be less than the device's
+ runTest(testActivity, actual -> {
+ if (aspectRatioEqual(expected, actual) || expected < actual) return;
+ fail("actual=" + actual + " is less than expected=" + expected);
+ });
+ }
+
+ @Test
+ public void testMaxAspectRatioUnsetActivity() throws Exception {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final float expected = getAspectRatio(context);
+
+ // Since this activity didn't set an aspect ratio, its aspect ratio shouldn't be less than
+ // the device's
+ runAspectRatioTest(mMaxAspectRatioUnsetActivity, actual -> {
+ if (aspectRatioEqual(expected, actual) || expected < actual) return;
+ fail("actual=" + actual + " is less than expected=" + expected);
+ });
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
new file mode 100644
index 0000000..6d1dc53
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTestsBase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.view.Display;
+import android.view.WindowManager;
+
+class AspectRatioTestsBase {
+
+ interface AssertAspectRatioCallback {
+ void assertAspectRatio(float actual);
+ }
+
+ void runAspectRatioTest(final ActivityTestRule activityRule,
+ final AssertAspectRatioCallback callback) {
+ final Activity activity = launchActivity(activityRule);
+ runTest(activity, callback);
+ finishActivity(activityRule);
+
+ // TODO(b/35810513): All this rotation stuff doesn't really work yet. Need to make sure
+ // context is updated correctly here. Also, what does it mean to be holding a reference to
+ // this activity if changing the orientation will cause a relaunch?
+// activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+// waitForIdle();
+// callback.assertAspectRatio(getAspectRatio(activity));
+//
+// activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+// waitForIdle();
+// callback.assertAspectRatio(getAspectRatio(activity));
+ }
+
+ protected void runTest(Activity activity, AssertAspectRatioCallback callback) {
+ callback.assertAspectRatio(getAspectRatio(activity));
+ }
+
+ static float getAspectRatio(Context context) {
+ final Display display =
+ context.getSystemService(WindowManager.class).getDefaultDisplay();
+ final Point size = new Point();
+ display.getSize(size);
+ final float longSide = Math.max(size.x, size.y);
+ final float shortSide = Math.min(size.x, size.y);
+ return longSide / shortSide;
+ }
+
+ protected Activity launchActivity(final ActivityTestRule activityRule) {
+ final Activity activity = activityRule.launchActivity(null);
+ waitForIdle();
+ return activity;
+ }
+
+ private void finishActivity(ActivityTestRule activityRule) {
+ final Activity activity = activityRule.getActivity();
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ static boolean aspectRatioEqual(float a, float b) {
+ // Aspect ratios are considered equal if they ware within to significant digits.
+ float diff = Math.abs(a - b);
+ return diff < 0.01f;
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
new file mode 100644
index 0000000..478c0b6
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.os.Build;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Ensure that compatibility dialog is shown when launching an application with
+ * an unsupported smallest width.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:DisplaySizeTest
+ */
+public class DisplaySizeTest extends ActivityManagerTestBase {
+ private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
+ private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
+
+ private static final ComponentName TEST_ACTIVITY = ComponentName.createRelative(
+ "android.server.am", ".TestActivity");
+ private static final ComponentName SMALLEST_WIDTH_ACTIVITY = ComponentName.createRelative(
+ "android.server.am.displaysize", ".SmallestWidthActivity");
+ /** @see android.server.am.displaysize.SmallestWidthActivity#EXTRA_LAUNCH_ANOTHER_ACTIVITY */
+ private static final String EXTRA_LAUNCH_ANOTHER_ACTIVITY = "launch_another_activity";
+
+ /** @see com.android.server.am.UnsupportedDisplaySizeDialog */
+ private static final String UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME =
+ "UnsupportedDisplaySizeDialog";
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ resetDensity();
+
+ // Ensure app process is stopped.
+ stopTestPackage(SMALLEST_WIDTH_ACTIVITY);
+ stopTestPackage(TEST_ACTIVITY);
+ }
+
+ @Test
+ public void testCompatibilityDialog() throws Exception {
+ // Launch some other app (not to perform density change on launcher).
+ executeShellCommand(getAmStartCmd(TEST_ACTIVITY));
+ assertActivityDisplayed(TEST_ACTIVITY);
+
+ setUnsupportedDensity();
+
+ // Launch target app.
+ executeShellCommand(getAmStartCmd(SMALLEST_WIDTH_ACTIVITY));
+ assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+ assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+ }
+
+ @Test
+ public void testCompatibilityDialogWhenFocused() throws Exception {
+ executeShellCommand(getAmStartCmd(SMALLEST_WIDTH_ACTIVITY));
+ assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+
+ setUnsupportedDensity();
+
+ assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+ }
+
+ @Test
+ public void testCompatibilityDialogAfterReturn() throws Exception {
+ // Launch target app.
+ executeShellCommand(getAmStartCmd(SMALLEST_WIDTH_ACTIVITY));
+ assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+ // Launch another activity.
+ final String startActivityOnTop = String.format("%s -f 0x%x --es %s %s",
+ getAmStartCmd(SMALLEST_WIDTH_ACTIVITY), FLAG_ACTIVITY_SINGLE_TOP,
+ EXTRA_LAUNCH_ANOTHER_ACTIVITY, TEST_ACTIVITY.flattenToShortString());
+ executeShellCommand(startActivityOnTop);
+ assertActivityDisplayed(TEST_ACTIVITY);
+
+ setUnsupportedDensity();
+
+ pressBackButton();
+
+ assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+ assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+ }
+
+ private void setUnsupportedDensity() {
+ // Set device to 0.85 zoom. It doesn't matter that we're zooming out
+ // since the feature verifies that we're in a non-default density.
+ final int stableDensity = getStableDensity();
+ final int targetDensity = (int) (stableDensity * 0.85);
+ setDensity(targetDensity);
+ }
+
+ private int getStableDensity() {
+ final String densityProp;
+ if (Build.getSerial().startsWith("emulator-")) {
+ densityProp = DENSITY_PROP_EMULATOR;
+ } else {
+ densityProp = DENSITY_PROP_DEVICE;
+ }
+
+ return Integer.parseInt(executeShellCommand("getprop " + densityProp).trim());
+ }
+
+ private void setDensity(int targetDensity) {
+ executeShellCommand("wm density " + targetDensity);
+
+ // Verify that the density is changed.
+ final String output = executeShellCommand("wm density");
+ final boolean success = output.contains("Override density: " + targetDensity);
+
+ assertTrue("Failed to set density to " + targetDensity, success);
+ }
+
+ private void resetDensity() {
+ executeShellCommand("wm density reset");
+ }
+
+ private void assertActivityDisplayed(final ComponentName activityName) throws Exception {
+ assertWindowDisplayed(activityName.flattenToString());
+ }
+
+ private void assertWindowDisplayed(final String windowName) throws Exception {
+ mAmWmState.waitForValidState(WaitForValidActivityState.forWindow(windowName));
+ assertTrue(windowName + "is visible", mAmWmState.getWmState().isWindowVisible(windowName));
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
new file mode 100644
index 0000000..55c8ebf
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:KeyguardLockedTests
+ */
+public class KeyguardLockedTests extends KeyguardTestBase {
+
+ private static final String SHOW_WHEN_LOCKED_ACTIVITY = "ShowWhenLockedActivity";
+ private static final String PIP_ACTIVITY = "PipActivity";
+ private static final String PIP_ACTIVITY_ACTION_ENTER_PIP =
+ "android.server.am.PipActivity.enter_pip";
+ private static final String EXTRA_SHOW_OVER_KEYGUARD = "show_over_keyguard";
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(isHandheld());
+
+ setLockCredential();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ tearDownLockCredentials();
+ }
+
+ @Test
+ public void testLockAndUnlock() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ unlockDeviceWithCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ }
+
+ @Test
+ public void testDismissKeyguard() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity("DismissKeyguardActivity");
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+ }
+
+ @Test
+ public void testDismissKeyguard_whileOccluded() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( SHOW_WHEN_LOCKED_ACTIVITY ).build());
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ launchActivity("DismissKeyguardActivity");
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
+ }
+
+ @Test
+ public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( SHOW_WHEN_LOCKED_ACTIVITY ).build());
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
+ enterAndConfirmLockCredential();
+
+ // Make sure we stay on Keyguard.
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ }
+
+ @Test
+ public void testDismissKeyguardActivity_method() throws Exception {
+ final String logSeparator = clearLogcat();
+ gotoKeyguard();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity("DismissKeyguardMethodActivity");
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "DismissKeyguardMethodActivity").build());
+ mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceededInLogcat(logSeparator);
+ }
+
+ @Test
+ public void testDismissKeyguardActivity_method_cancelled() throws Exception {
+ final String logSeparator = clearLogcat();
+ gotoKeyguard();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity("DismissKeyguardMethodActivity");
+ pressBackButton();
+ assertOnDismissCancelledInLogcat(logSeparator);
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("DismissKeyguardMethodActivity", false);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ unlockDeviceWithCredential();
+ }
+
+ @Test
+ public void testEnterPipOverKeyguard() throws Exception {
+ assumeTrue(supportsPip());
+
+ // Go to the keyguard
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+
+ // Enter PiP on an activity on top of the keyguard, and ensure that it prompts the user for
+ // their credentials and does not enter picture-in-picture yet
+ launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.waitForKeyguardShowingAndOccluded();
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ // Enter the credentials and ensure that the activity actually entered picture-in-picture
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ public void testShowWhenLockedActivityAndPipActivity() throws Exception {
+ assumeTrue(supportsPip());
+
+ launchActivity(PIP_ACTIVITY);
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(PIP_ACTIVITY).build());
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+ launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+ mAmWmState.computeState(
+ new WaitForValidActivityState.Builder(SHOW_WHEN_LOCKED_ACTIVITY).build());
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndOccluded();
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+ }
+
+ @Test
+ public void testShowWhenLockedPipActivity() throws Exception {
+ assumeTrue(supportsPip());
+
+ launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+ executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(PIP_ACTIVITY).build());
+ mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+ ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
new file mode 100644
index 0000000..9a63396
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class KeyguardTestBase extends ActivityManagerTestBase {
+
+ protected void assertOnDismissSucceededInLogcat(String logSeparator) throws Exception {
+ assertInLogcat("KeyguardDismissLoggerCallback", "onDismissSucceeded", logSeparator);
+ }
+
+ protected void assertOnDismissCancelledInLogcat(String logSeparator) throws Exception {
+ assertInLogcat("KeyguardDismissLoggerCallback", "onDismissCancelled", logSeparator);
+ }
+
+ protected void assertOnDismissErrorInLogcat(String logSeparator) throws Exception {
+ assertInLogcat("KeyguardDismissLoggerCallback", "onDismissError", logSeparator);
+ }
+
+ private void assertInLogcat(String activityName, String entry, String logSeparator)
+ throws Exception {
+ final Pattern pattern = Pattern.compile("(.+)" + entry);
+ int tries = 0;
+ while (tries < 5) {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ log("Looking at logcat");
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ log(line);
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches()) {
+ return;
+ }
+ }
+ tries++;
+ Thread.sleep(500);
+ }
+ fail("Not in logcat: " + entry);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
new file mode 100644
index 0000000..015c926
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.Surface.ROTATION_90;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:KeyguardTests
+ */
+public class KeyguardTests extends KeyguardTestBase {
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(isHandheld());
+ assertFalse(isUiModeLockedToVrHeadset());
+
+ // Set screen lock (swipe)
+ setLockDisabled(false);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ tearDownLockCredentials();
+ }
+
+ @Test
+ public void testKeyguardHidesActivity() throws Exception {
+ launchActivity("TestActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "TestActivity").build());
+ mAmWmState.assertVisibility("TestActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility("TestActivity", false);
+ unlockDevice();
+ }
+
+ @Test
+ public void testShowWhenLockedActivity() throws Exception {
+ launchActivity("ShowWhenLockedActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ /**
+ * Tests whether dialogs from SHOW_WHEN_LOCKED activities are also visible if Keyguard is
+ * showing.
+ */
+ @Test
+ public void testShowWhenLockedActivity_withDialog() throws Exception {
+ launchActivity("ShowWhenLockedWithDialogActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedWithDialogActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
+ assertTrue(mAmWmState.getWmState().allWindowsVisible(
+ getWindowName("ShowWhenLockedWithDialogActivity")));
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ /**
+ * Tests whether multiple SHOW_WHEN_LOCKED activities are shown if the topmost is translucent.
+ */
+ @Test
+ public void testMultipleShowWhenLockedActivities() throws Exception {
+ launchActivity("ShowWhenLockedActivity");
+ launchActivity("ShowWhenLockedTranslucentActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedActivity").build(),
+ new WaitForValidActivityState.Builder("ShowWhenLockedTranslucentActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ /**
+ * If we have a translucent SHOW_WHEN_LOCKED_ACTIVITY, the wallpaper should also be showing.
+ */
+ @Test
+ public void testTranslucentShowWhenLockedActivity() throws Exception {
+ launchActivity("ShowWhenLockedTranslucentActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedTranslucentActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+ assertWallpaperShowing();
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ /**
+ * If we have a translucent SHOW_WHEN_LOCKED activity, the activity behind should not be shown.
+ */
+ @Test
+ public void testTranslucentDoesntRevealBehind() throws Exception {
+ launchActivity("TestActivity");
+ launchActivity("ShowWhenLockedTranslucentActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("TestActivity").build(),
+ new WaitForValidActivityState.Builder("ShowWhenLockedTranslucentActivity").build());
+ mAmWmState.assertVisibility("TestActivity", true);
+ mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+ mAmWmState.assertVisibility("TestActivity", false);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ @Test
+ public void testDialogShowWhenLockedActivity() throws Exception {
+ launchActivity("ShowWhenLockedDialogActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedDialogActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState();
+ mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
+ assertWallpaperShowing();
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ /**
+ * Test that showWhenLocked activity is fullscreen when shown over keyguard
+ */
+ @Test
+ @Presubmit
+ public void testShowWhenLockedActivityWhileSplit() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+ getLaunchActivityBuilder().setTargetActivityName("ShowWhenLockedActivity")
+ .setRandomData(true)
+ .setMultipleTask(false)
+ );
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ gotoKeyguard();
+ mAmWmState.computeState(
+ new WaitForValidActivityState.Builder("ShowWhenLockedActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ pressHomeButton();
+ unlockDevice();
+ }
+
+ /**
+ * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
+ */
+ @Test
+ public void testDismissKeyguardActivity() throws Exception {
+ gotoKeyguard();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity("DismissKeyguardActivity");
+ mAmWmState.waitForKeyguardShowingAndOccluded();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "DismissKeyguardActivity").build());
+ mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ }
+
+ @Test
+ public void testDismissKeyguardActivity_method() throws Exception {
+ final String logSeparator = clearLogcat();
+ gotoKeyguard();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity("DismissKeyguardMethodActivity");
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "DismissKeyguardMethodActivity").build());
+ mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceededInLogcat(logSeparator);
+ }
+
+ @Test
+ public void testDismissKeyguardActivity_method_notTop() throws Exception {
+ final String logSeparator = clearLogcat();
+ gotoKeyguard();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity("BroadcastReceiverActivity");
+ launchActivity("TestActivity");
+ executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguardMethod true");
+ assertOnDismissErrorInLogcat(logSeparator);
+ }
+
+ @Test
+ public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
+ final String logSeparator = clearLogcat();
+ sleepDevice();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity("TurnScreenOnDismissKeyguardActivity");
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "TurnScreenOnDismissKeyguardActivity").build());
+ mAmWmState.assertVisibility("TurnScreenOnDismissKeyguardActivity", true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceededInLogcat(logSeparator);
+ }
+
+ @Test
+ public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity("ShowWhenLockedActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedActivity" ).build());
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ }
+
+ @Test
+ public void testKeyguardLock() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity("KeyguardLockActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "KeyguardLockActivity" ).build());
+ mAmWmState.assertVisibility("KeyguardLockActivity", true);
+ executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ }
+
+ @Test
+ public void testUnoccludeRotationChange() throws Exception {
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ executeShellCommand(getAmStartCmd("ShowWhenLockedActivity"));
+ mAmWmState.computeState(new WaitForValidActivityState("ShowWhenLockedActivity"));
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ try (final RotationSession rotationSession = new RotationSession()) {
+ rotationSession.set(ROTATION_90);
+ pressHomeButton();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.waitForDisplayUnfrozen();
+ mAmWmState.assertSanity();
+ mAmWmState.assertHomeActivityVisible(false);
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", false);
+ }
+ }
+
+ private void assertWallpaperShowing() {
+ WindowState wallpaper =
+ mAmWmState.getWmState().findFirstWindowWithType(TYPE_WALLPAPER);
+ assertNotNull(wallpaper);
+ assertTrue(wallpaper.isShown());
+ }
+
+ @Test
+ public void testDismissKeyguardAttrActivity_method_turnScreenOn() throws Exception {
+ final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
+ sleepDevice();
+
+ final String logSeparator = clearLogcat();
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(activityName);
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertVisibility(activityName, true);
+ assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertOnDismissSucceededInLogcat(logSeparator);
+ assertTrue(isDisplayOn());
+ }
+
+ @Test
+ public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() throws Exception {
+ final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
+
+ setLockCredential();
+ sleepDevice();
+
+ mAmWmState.computeState();
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ launchActivity(activityName);
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertVisibility(activityName, false);
+ assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+ assertTrue(isDisplayOn());
+ }
+
+ @Test
+ public void testScreenOffWhileOccludedStopsActivity() throws Exception {
+ final String logSeparator = clearLogcat();
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity("ShowWhenLockedAttrActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedAttrActivity" ).build());
+ mAmWmState.assertVisibility("ShowWhenLockedAttrActivity", true);
+ mAmWmState.assertKeyguardShowingAndOccluded();
+ sleepDevice();
+ assertSingleLaunchAndStop("ShowWhenLockedAttrActivity", logSeparator);
+ }
+
+ @Test
+ public void testScreenOffCausesSingleStop() throws Exception {
+ final String logSeparator = clearLogcat();
+ launchActivity("TestActivity");
+ mAmWmState.assertVisibility("TestActivity", true);
+ sleepDevice();
+ assertSingleLaunchAndStop("TestActivity", logSeparator);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
new file mode 100644
index 0000000..cb0d5b5e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.WindowManagerState.TRANSIT_ACTIVITY_OPEN;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.server.am.WindowManagerState.TRANSIT_KEYGUARD_UNOCCLUDE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:KeyguardTransitionTests
+ */
+public class KeyguardTransitionTests extends ActivityManagerTestBase {
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(isHandheld());
+ assumeFalse(isUiModeLockedToVrHeadset());
+
+ // Set screen lock (swipe)
+ setLockDisabled(false);
+ }
+
+ @Test
+ public void testUnlock() throws Exception {
+ launchActivity("TestActivity");
+ gotoKeyguard();
+ unlockDevice();
+ mAmWmState.computeState(new WaitForValidActivityState("TestActivity"));
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
+ mAmWmState.getWmState().getLastTransition());
+ }
+ @Test
+ public void testUnlockWallpaper() throws Exception {
+ launchActivity("WallpaperActivity");
+ gotoKeyguard();
+ unlockDevice();
+ mAmWmState.computeState(new WaitForValidActivityState("WallpaperActivity"));
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ mAmWmState.getWmState().getLastTransition());
+ }
+ @Test
+ public void testOcclude() throws Exception {
+ gotoKeyguard();
+ launchActivity("ShowWhenLockedActivity");
+ mAmWmState.computeState(new WaitForValidActivityState("ShowWhenLockedActivity"));
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getLastTransition());
+ }
+ @Test
+ public void testUnocclude() throws Exception {
+ gotoKeyguard();
+ launchActivity("ShowWhenLockedActivity");
+ launchActivity("TestActivity");
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.computeState();
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
+ mAmWmState.getWmState().getLastTransition());
+ }
+ @Test
+ public void testNewActivityDuringOccluded() throws Exception {
+ launchActivity("ShowWhenLockedActivity");
+ gotoKeyguard();
+ launchActivity("ShowWhenLockedWithDialogActivity");
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedWithDialogActivity").build());
+ assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
+ mAmWmState.getWmState().getLastTransition());
+ }
+ @Test
+ public void testOccludeManifestAttr() throws Exception {
+ String activityName = "ShowWhenLockedAttrActivity";
+
+ gotoKeyguard();
+ final String logSeparator = clearLogcat();
+ launchActivity(activityName);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getLastTransition());
+ assertSingleLaunch(activityName, logSeparator);
+ }
+ @Test
+ public void testOccludeAttrRemove() throws Exception {
+ String activityName = "ShowWhenLockedAttrRemoveAttrActivity";
+
+ gotoKeyguard();
+ String logSeparator = clearLogcat();
+ launchActivity(activityName);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getLastTransition());
+ assertSingleLaunch(activityName, logSeparator);
+
+ gotoKeyguard();
+ logSeparator = clearLogcat();
+ launchActivity(activityName);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+ assertSingleStartAndStop(activityName, logSeparator);
+ }
+ @Test
+ public void testNewActivityDuringOccludedWithAttr() throws Exception {
+ String activityName1 = "ShowWhenLockedAttrActivity";
+ String activityName2 = "ShowWhenLockedAttrWithDialogActivity";
+
+ launchActivity(activityName1);
+ gotoKeyguard();
+ launchActivity(activityName2);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName2).build());
+ assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
+ mAmWmState.getWmState().getLastTransition());
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
new file mode 100644
index 0000000..6e98083
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Ensure that compatibility dialog is shown when launching an application built
+ * against a prerelease SDK.
+ * <p>Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:PrereleaseSdkTest
+ */
+public class PrereleaseSdkTest extends ActivityManagerTestBase {
+ private static final String AM_START_COMMAND = "am start -n %s/%s.%s";
+ private static final String AM_FORCE_STOP = "am force-stop %s";
+
+ private static final int ACTIVITY_TIMEOUT_MILLIS = 1000;
+ private static final int WINDOW_TIMEOUT_MILLIS = 1000;
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ // Ensure app process is stopped.
+ forceStopPackage("android.server.am.prerelease");
+ forceStopPackage("android.server.am");
+ }
+
+ @Test
+ public void testCompatibilityDialog() throws Exception {
+ // Launch target app.
+ startActivity("android.server.am.prerelease", "MainActivity");
+ verifyWindowDisplayed("MainActivity", ACTIVITY_TIMEOUT_MILLIS);
+ verifyWindowDisplayed("UnsupportedCompileSdkDialog", WINDOW_TIMEOUT_MILLIS);
+
+ // Go back to dismiss the warning dialog.
+ executeShellCommand("input keyevent 4");
+
+ // Go back again to formally stop the app. If we just kill the process, it'll attempt to
+ // resume rather than starting from scratch (as far as ActivityStack is concerned) and it
+ // won't invoke the warning dialog.
+ executeShellCommand("input keyevent 4");
+ }
+
+ private void forceStopPackage(String packageName) {
+ final String forceStopCmd = String.format(AM_FORCE_STOP, packageName);
+ executeShellCommand(forceStopCmd);
+ }
+
+ private void startActivity(String packageName, String activityName){
+ executeShellCommand(getStartCommand(packageName, activityName));
+ }
+
+ private String getStartCommand(String packageName, String activityName) {
+ return String.format(AM_START_COMMAND, packageName, packageName, activityName);
+ }
+
+ private void verifyWindowDisplayed(String windowName, long timeoutMillis) {
+ boolean success = false;
+
+ // Verify that compatibility dialog is shown within 1000ms.
+ final long timeoutTimeMillis = System.currentTimeMillis() + timeoutMillis;
+ while (!success && System.currentTimeMillis() < timeoutTimeMillis) {
+ final String output = executeShellCommand("dumpsys window");
+ success = output.contains(windowName);
+ }
+
+ assertTrue(windowName + " was not displayed", success);
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
new file mode 100644
index 0000000..f4edf0f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:SplashscreenTests
+ */
+public class SplashscreenTests extends ActivityManagerTestBase {
+
+ @Test
+ public void testSplashscreenContent() throws Exception {
+ launchActivityNoWait("SplashscreenActivity");
+ mAmWmState.waitForAppTransitionIdle();
+ mAmWmState.getWmState().getStableBounds();
+ final Bitmap image = takeScreenshot();
+ // Use ratios to flexibly accomodate circular or not quite rectangular displays
+ // Note: Color.BLACK is the pixel color outside of the display region
+ assertColors(image, mAmWmState.getWmState().getStableBounds(),
+ Color.RED, 0.50f, Color.BLACK, 0.01f);
+ }
+
+ private void assertColors(Bitmap img, Rect bounds, int primaryColor,
+ float expectedPrimaryRatio, int secondaryColor, float acceptableWrongRatio) {
+
+ int primaryPixels = 0;
+ int secondaryPixels = 0;
+ int wrongPixels = 0;
+ for (int x = bounds.left; x < bounds.right; x++) {
+ for (int y = bounds.top; y < bounds.bottom; y++) {
+ assertTrue(x < img.getWidth());
+ assertTrue(y < img.getHeight());
+ final int color = img.getPixel(x, y);
+ if (primaryColor == color) {
+ primaryPixels++;
+ } else if (secondaryColor == color) {
+ secondaryPixels++;
+ } else {
+ wrongPixels++;
+ }
+ }
+ }
+
+ final int totalPixels = bounds.width() * bounds.height();
+ final float primaryRatio = (float) primaryPixels / totalPixels;
+ if (primaryRatio < expectedPrimaryRatio) {
+ fail("Less than " + (expectedPrimaryRatio * 100.0f)
+ + "% of pixels have non-primary color primaryPixels=" + primaryPixels
+ + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
+ }
+ // Some pixels might be covered by screen shape decorations, like rounded corners.
+ // On circular displays, there is an antialiased edge.
+ final float wrongRatio = (float) wrongPixels / totalPixels;
+ if (wrongRatio > acceptableWrongRatio) {
+ fail("More than " + (acceptableWrongRatio * 100.0f)
+ + "% of pixels have wrong color primaryPixels=" + primaryPixels
+ + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
new file mode 100644
index 0000000..a5673a6
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -0,0 +1,83 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.server.am.ActivityManagerTestBase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
+import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Before;
+
+/** Base class for device-side tests that verify correct activity lifecycle transitions. */
+public class ActivityLifecycleClientTestBase extends ActivityManagerTestBase {
+
+ final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule(FirstActivity.class,
+ true /* initialTouchMode */, false /* launchActivity */);
+
+ final ActivityTestRule mSecondActivityTestRule = new ActivityTestRule(SecondActivity.class,
+ true /* initialTouchMode */, false /* launchActivity */);
+
+ private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
+ .getInstance();
+ private LifecycleLog mLifecycleLog;
+ private LifecycleTracker mLifecycleTracker;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // Log transitions for all activities that belong to this app.
+ mLifecycleLog = new LifecycleLog();
+ mLifecycleMonitor.addLifecycleCallback(mLifecycleLog);
+
+ // Track transitions and allow waiting for pending activity states.
+ mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
+ mLifecycleMonitor.addLifecycleCallback(mLifecycleTracker);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ mLifecycleMonitor.removeLifecycleCallback(mLifecycleLog);
+ mLifecycleMonitor.removeLifecycleCallback(mLifecycleTracker);
+ super.tearDown();
+ }
+
+ /** Launch an activity given a class. */
+ protected Activity launchActivity(Class<? extends Activity> activityClass) {
+ final Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), activityClass);
+ return InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+ }
+
+ /**
+ * Blocking call that will wait for activities to reach expected states with timeout.
+ */
+ @SafeVarargs
+ final void waitAndAssertActivityStates(Pair<Activity, Stage>... activityStates) {
+ log("Start waitAndAssertActivityStates");
+ mLifecycleTracker.waitAndAssertActivityStates(activityStates);
+ }
+
+ LifecycleLog getLifecycleLog() {
+ return mLifecycleLog;
+ }
+
+ static Pair<Activity, Stage> state(Activity activity, Stage stage) {
+ return new Pair<>(activity, stage);
+ }
+
+ // Test activity
+ public static class FirstActivity extends Activity {
+ }
+
+ // Test activity
+ public static class SecondActivity extends Activity {
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
new file mode 100644
index 0000000..982f30d
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -0,0 +1,46 @@
+package android.server.am.lifecycle;
+
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityLifecycleKeyguardTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ActivityLifecycleKeyguardTests extends ActivityLifecycleClientTestBase {
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ setLockCredential();
+ gotoKeyguard();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ tearDownLockCredentials();
+ super.tearDown();
+ }
+
+ @Test
+ public void testSingleLaunch() throws Exception {
+ final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+ waitAndAssertActivityStates(state(activity, STOPPED));
+
+ LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
new file mode 100644
index 0000000..4ec99fa
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -0,0 +1,71 @@
+package android.server.am.lifecycle;
+
+import static android.support.test.runner.lifecycle.Stage.DESTROYED;
+import static android.support.test.runner.lifecycle.Stage.RESUMED;
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:ActivityLifecycleTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ActivityLifecycleTests extends ActivityLifecycleClientTestBase {
+
+ @Test
+ public void testSingleLaunch() throws Exception {
+ final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+ waitAndAssertActivityStates(state(activity, RESUMED));
+
+ LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
+ }
+
+ @Test
+ public void testLaunchOnTop() throws Exception {
+ final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+ waitAndAssertActivityStates(state(firstActivity, RESUMED));
+
+ getLifecycleLog().clear();
+ final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
+ waitAndAssertActivityStates(state(firstActivity, STOPPED),
+ state(secondActivity, RESUMED));
+
+ LifecycleVerifier.assertLaunchSequence(SecondActivity.class, FirstActivity.class,
+ getLifecycleLog());
+ }
+
+ @FlakyTest(bugId = 70649184)
+ @Test
+ public void testLaunchAndDestroy() throws Exception {
+ final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+
+ activity.finish();
+ waitAndAssertActivityStates(state(activity, DESTROYED));
+
+ LifecycleVerifier.assertLaunchAndDestroySequence(FirstActivity.class, getLifecycleLog());
+ }
+
+ @Test
+ public void testRelaunch() throws Exception {
+ final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+ waitAndAssertActivityStates(state(activity, RESUMED));
+
+ getLifecycleLog().clear();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(activity::recreate);
+ waitAndAssertActivityStates(state(activity, RESUMED));
+
+ LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog());
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
new file mode 100644
index 0000000..dab6316
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -0,0 +1,50 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+
+import android.app.Activity;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used as a shared log storage of activity lifecycle transitions.
+ */
+class LifecycleLog implements ActivityLifecycleCallback {
+
+ private List<Pair<String, Stage>> mLog = new ArrayList<>();
+
+ /** Clear the entire transition log. */
+ void clear() {
+ mLog.clear();
+ }
+
+ /** Add transition of an activity to the log. */
+ @Override
+ public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+ final String activityName = activity.getClass().getCanonicalName();
+ log("Activity " + activityName + " moved to stage " + stage);
+ mLog.add(new Pair<>(activityName, stage));
+ }
+
+ /** Get logs for all recorded transitions. */
+ List<Pair<String, Stage>> getLog() {
+ return mLog;
+ }
+
+ /** Get transition logs for the specified activity. */
+ List<Stage> getActivityLog(Class<? extends Activity> activityClass) {
+ final String activityName = activityClass.getCanonicalName();
+ log("Looking up log for activity: " + activityName);
+ final List<Stage> activityLog = new ArrayList<>();
+ for (Pair<String, Stage> transition : mLog) {
+ if (transition.first.equals(activityName)) {
+ activityLog.add(transition.second);
+ }
+ }
+ return activityLog;
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
new file mode 100644
index 0000000..df2cff7
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
@@ -0,0 +1,76 @@
+package android.server.am.lifecycle;
+
+import static org.junit.Assert.fail;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.support.test.runner.lifecycle.ActivityLifecycleCallback;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Gets notified about activity lifecycle updates and provides blocking mechanism to wait until
+ * expected activity states are reached.
+ */
+public class LifecycleTracker implements ActivityLifecycleCallback {
+
+ private LifecycleLog mLifecycleLog;
+
+ LifecycleTracker(LifecycleLog lifecycleLog) {
+ mLifecycleLog = lifecycleLog;
+ }
+
+ void waitAndAssertActivityStates(Pair<Activity, Stage>[] activityStates) {
+ final boolean waitResult = waitForConditionWithTimeout(
+ () -> pendingStates(activityStates).isEmpty(), 5 * 1000);
+
+ if (!waitResult) {
+ fail("Expected lifecycle states not achieved: " + pendingStates(activityStates));
+ }
+ }
+
+ @Override
+ synchronized public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+ notify();
+ }
+
+ /** Get a list of activity states that were not reached yet. */
+ private List<Pair<Activity, Stage>> pendingStates(Pair<Activity, Stage>[] activityStates) {
+ final List<Pair<Activity, Stage>> notReachedActivityStates = new ArrayList<>();
+
+ for (Pair<Activity, Stage> activityState : activityStates) {
+ final Activity activity = activityState.first;
+ final List<Stage> transitionList = mLifecycleLog.getActivityLog(activity.getClass());
+ if (transitionList.isEmpty()
+ || transitionList.get(transitionList.size() - 1) != activityState.second) {
+ // The activity either hasn't got any state transitions yet or the current state is
+ // not the one we expect.
+ notReachedActivityStates.add(activityState);
+ }
+ }
+ return notReachedActivityStates;
+ }
+
+ /** Blocking call to wait for a condition to become true with max timeout. */
+ synchronized private boolean waitForConditionWithTimeout(BooleanSupplier waitCondition,
+ long timeoutMs) {
+ final long timeout = System.currentTimeMillis() + timeoutMs;
+ while (!waitCondition.getAsBoolean()) {
+ final long waitMs = timeout - System.currentTimeMillis();
+ if (waitMs <= 0) {
+ // Timeout expired.
+ return false;
+ }
+ try {
+ wait(timeoutMs);
+ } catch (InterruptedException e) {
+ // Weird, let's retry.
+ }
+ }
+ return true;
+ }
+}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
new file mode 100644
index 0000000..bbb3ad8
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
@@ -0,0 +1,94 @@
+package android.server.am.lifecycle;
+
+import static android.support.test.runner.lifecycle.Stage.CREATED;
+import static android.support.test.runner.lifecycle.Stage.DESTROYED;
+import static android.support.test.runner.lifecycle.Stage.PAUSED;
+import static android.support.test.runner.lifecycle.Stage.PRE_ON_CREATE;
+import static android.support.test.runner.lifecycle.Stage.RESUMED;
+import static android.support.test.runner.lifecycle.Stage.STARTED;
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+import android.app.Activity;
+import android.support.test.runner.lifecycle.Stage;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static android.server.am.StateLogger.log;
+import static org.junit.Assert.assertEquals;
+
+/** Util class that verifies correct activity state transition sequences. */
+class LifecycleVerifier {
+
+ static void assertLaunchSequence(Class<? extends Activity> activityClass,
+ LifecycleLog lifecycleLog) {
+ final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+ log("Observed sequence: " + observedTransitions);
+ final String errorMessage = errorDuringTransition(activityClass, "launch");
+
+ final List<Stage> expectedTransitions =
+ Arrays.asList(PRE_ON_CREATE, CREATED, STARTED, RESUMED);
+ assertEquals(errorMessage, expectedTransitions, observedTransitions);
+ }
+
+ static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
+ Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog) {
+ final List<Pair<String, Stage>> observedTransitions = lifecycleLog.getLog();
+ log("Observed sequence: " + observedTransitions);
+ final String errorMessage = errorDuringTransition(launchingActivity, "launch");
+
+ final List<Pair<String, Stage>> expectedTransitions = Arrays.asList(
+ transition(existingActivity, PAUSED),
+ transition(launchingActivity, PRE_ON_CREATE),
+ transition(launchingActivity, CREATED),
+ transition(launchingActivity, STARTED),
+ transition(launchingActivity, RESUMED),
+ transition(existingActivity, STOPPED));
+ assertEquals(errorMessage, expectedTransitions, observedTransitions);
+ }
+
+ static void assertLaunchAndStopSequence(Class<? extends Activity> activityClass,
+ LifecycleLog lifecycleLog) {
+ final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+ log("Observed sequence: " + observedTransitions);
+ final String errorMessage = errorDuringTransition(activityClass, "launch and stop");
+
+ final List<Stage> expectedTransitions =
+ Arrays.asList(PRE_ON_CREATE, CREATED, STARTED, RESUMED, PAUSED, STOPPED);
+ assertEquals(errorMessage, expectedTransitions, observedTransitions);
+ }
+
+ static void assertLaunchAndDestroySequence(Class<? extends Activity> activityClass,
+ LifecycleLog lifecycleLog) {
+ final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+ log("Observed sequence: " + observedTransitions);
+ final String errorMessage = errorDuringTransition(activityClass, "launch and destroy");
+
+ final List<Stage> expectedTransitions =
+ Arrays.asList(PRE_ON_CREATE, CREATED, STARTED, RESUMED, PAUSED, STOPPED, DESTROYED);
+ assertEquals(errorMessage, expectedTransitions, observedTransitions);
+ }
+
+ static void assertRelaunchSequence(Class<? extends Activity> activityClass,
+ LifecycleLog lifecycleLog) {
+ final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+ log("Observed sequence: " + observedTransitions);
+ final String errorMessage = errorDuringTransition(activityClass, "relaunch");
+
+ final List<Stage> expectedTransitions =
+ Arrays.asList(PAUSED, STOPPED, DESTROYED, PRE_ON_CREATE, CREATED, STARTED, RESUMED);
+ assertEquals(errorMessage, expectedTransitions, observedTransitions);
+ }
+
+ private static Pair<String, Stage> transition(
+ Class<? extends Activity> activityClass, Stage state) {
+ return new Pair<>(activityClass.getCanonicalName(), state);
+ }
+
+ private static String errorDuringTransition(Class<? extends Activity> activityClass,
+ String transition) {
+ return "Failed verification during moving activity: " + activityClass.getCanonicalName()
+ + " through transition: " + transition;
+ }
+}
diff --git a/tests/framework/base/activitymanager/testsdk25/Android.mk b/tests/framework/base/activitymanager/testsdk25/Android.mk
new file mode 100644
index 0000000..e55d5f0
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+LOCAL_PACKAGE_NAME := CtsActivityManagerDeviceSdk25TestCases
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../src/android/server/am/AspectRatioTestsBase.java
+
+LOCAL_SDK_VERSION := 25
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/framework/base/activitymanager/testsdk25/AndroidManifest.xml b/tests/framework/base/activitymanager/testsdk25/AndroidManifest.xml
new file mode 100644
index 0000000..f216411
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.cts.am.testsdk25">
+
+ <uses-sdk android:targetSdkVersion="25" />
+
+ <application android:label="CtsActivityManagerDeviceSdk25TestCases">
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="android.server.am.AspectRatioSdk25Tests$Sdk25MaxAspectRatioActivity"
+ android:label="Sdk25MaxAspectRatioActivity"
+ android:resizeableActivity="false" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.server.cts.am.testsdk25" />
+
+</manifest>
diff --git a/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml b/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
new file mode 100644
index 0000000..9bbec9e
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="Config for CTS ActivityManager SDK 25 compatibility test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsActivityManagerDeviceSdk25TestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.server.cts.am.testsdk25" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java b/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java
new file mode 100644
index 0000000..6ec1df0
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/src/android/server/am/AspectRatioSdk25Tests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base/activitymanager
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceSdk25TestCases android.server.am.AspectRatioSdk25Tests
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class AspectRatioSdk25Tests extends AspectRatioTestsBase {
+
+ // Max supported aspect ratio for pre-O apps.
+ private static final float MAX_PRE_O_ASPECT_RATIO = 1.86f;
+
+ // Test target activity that has targetSdk="25" and resizeableActivity="false".
+ public static class Sdk25MaxAspectRatioActivity extends Activity {
+ }
+
+ @Rule
+ public ActivityTestRule<Sdk25MaxAspectRatioActivity> mSdk25MaxAspectRatioActivity =
+ new ActivityTestRule<>(Sdk25MaxAspectRatioActivity.class,
+ false /* initialTouchMode */, false /* launchActivity */);
+
+ @Test
+ public void testMaxAspectRatioPreOActivity() throws Exception {
+ runAspectRatioTest(mSdk25MaxAspectRatioActivity, actual -> {
+ if (MAX_PRE_O_ASPECT_RATIO >= actual) return;
+ fail("actual=" + actual + " is greater than expected=" + MAX_PRE_O_ASPECT_RATIO);
+ });
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk b/tests/framework/base/activitymanager/translucentapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk
rename to tests/framework/base/activitymanager/translucentapp/Android.mk
diff --git a/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml b/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml
new file mode 100755
index 0000000..edf7129
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.translucentapp">
+
+ <application android:label="CtsTranslucentApp">
+ <activity
+ android:name=".TranslucentLandscapeActivity"
+ android:exported="true"
+ android:screenOrientation="landscape"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java
new file mode 100644
index 0000000..3d8de4e
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.translucentapp;
+
+import android.app.Activity;
+
+public class TranslucentLandscapeActivity extends Activity {
+}
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk b/tests/framework/base/activitymanager/translucentappsdk26/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk
rename to tests/framework/base/activitymanager/translucentappsdk26/Android.mk
diff --git a/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml b/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml
new file mode 100755
index 0000000..35c86cc
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.am.translucentapp26">
+
+ <application android:label="CtsTranslucentApp26">
+ <activity
+ android:name="android.server.am.translucentapp.TranslucentLandscapeActivity"
+ android:exported="true"
+ android:screenOrientation="landscape"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/framework/base/activitymanager/util/Android.mk b/tests/framework/base/activitymanager/util/Android.mk
new file mode 100644
index 0000000..6fa0331
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ platformprotosnano \
+ compatibility-device-util \
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
+
+LOCAL_MODULE := cts-amwm-util
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/services/activityandwindowmanager/util/run-test b/tests/framework/base/activitymanager/util/run-test
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/util/run-test
rename to tests/framework/base/activitymanager/util/run-test
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
new file mode 100644
index 0000000..7367875
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.server.am.ActivityManagerTestBase.componentName;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+import android.server.am.WindowManagerState.Display;
+import android.server.am.WindowManagerState.WindowStack;
+import android.server.am.WindowManagerState.WindowState;
+import android.server.am.WindowManagerState.WindowTask;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
+
+/**
+ * Combined state of the activity manager and window manager.
+ */
+public class ActivityAndWindowManagersState {
+
+ // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM)
+ // (Needed in host-side tests to convert dp to px.)
+ private static final int DISPLAY_DENSITY_DEFAULT = 160;
+ // TODO: Change to use framework constant.
+ public static final int DEFAULT_DISPLAY_ID = 0;
+
+ // Default minimal size of resizable task, used if none is set explicitly.
+ // Must be kept in sync with 'default_minimal_size_resizable_task' dimen from frameworks/base.
+ private static final int DEFAULT_RESIZABLE_TASK_SIZE_DP = 220;
+
+ // Default minimal size of a resizable PiP task, used if none is set explicitly.
+ // Must be kept in sync with 'default_minimal_size_pip_resizable_task' dimen from
+ // frameworks/base.
+ private static final int DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP = 108;
+
+ private ActivityManagerState mAmState = new ActivityManagerState();
+ private WindowManagerState mWmState = new WindowManagerState();
+
+ @Deprecated
+ public void computeState(String... waitForActivitiesVisible)
+ throws Exception {
+ WaitForValidActivityState[] states = waitForActivitiesVisible != null ?
+ new WaitForValidActivityState[waitForActivitiesVisible.length] : null;
+ if (states != null) {
+ for (int i = 0; i < waitForActivitiesVisible.length; i++) {
+ states[i] =
+ new WaitForValidActivityState.Builder(waitForActivitiesVisible[i]).build();
+ }
+ }
+ computeState(states);
+ }
+
+ @Deprecated
+ public void computeState() throws Exception {
+ computeState(true);
+ }
+
+ /**
+ * Compute AM and WM state of device, check sanity and bounds.
+ * WM state will include only visible windows, stack and task bounds will be compared.
+ *
+ * @param waitForActivitiesVisible array of activity names to wait for.
+ */
+ public void computeState(WaitForValidActivityState... waitForActivitiesVisible)
+ throws Exception {
+ computeState(true, waitForActivitiesVisible);
+ }
+
+ /**
+ * Compute AM and WM state of device, check sanity and bounds.
+ *
+ * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared,
+ * 'false' otherwise.
+ * @param waitForActivitiesVisible array of activity states to wait for.
+ */
+ void computeState(boolean compareTaskAndStackBounds,
+ WaitForValidActivityState... waitForActivitiesVisible) throws Exception {
+ waitForValidState(compareTaskAndStackBounds, waitForActivitiesVisible);
+ assertSanity();
+ assertValidBounds(compareTaskAndStackBounds);
+ }
+
+ /**
+ * Compute AM and WM state of device, wait for the activity records to be added, and
+ * wait for debugger window to show up.
+ *
+ * This should only be used when starting with -D (debugger) option, where we pop up the
+ * waiting-for-debugger window, but real activity window won't show up since we're waiting
+ * for debugger.
+ */
+ void waitForDebuggerWindowVisible(String[] waitForActivityRecords) {
+ int retriesLeft = 5;
+ do {
+ mAmState.computeState();
+ mWmState.computeState();
+ if (shouldWaitForDebuggerWindow() ||
+ shouldWaitForActivityRecords(waitForActivityRecords)) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+ }
+
+ /**
+ * Wait for the activity to appear and for valid state in AM and WM.
+ *
+ * @param waitForActivityVisible name of activity to wait for.
+ */
+ @Deprecated
+ void waitForValidState(String waitForActivityVisible)
+ throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState(waitForActivityVisible));
+ }
+
+ /** Wait for the activity to appear and for valid state in AM and WM. */
+ void waitForValidState(WaitForValidActivityState... waitForActivityVisible) throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */, waitForActivityVisible);
+ }
+
+ /**
+ * Wait for the activity to appear in proper stack and for valid state in AM and WM.
+ *
+ * @param waitForActivityVisible name of activity to wait for.
+ * @param stackId id of the stack where provided activity should be found.
+ */
+ @Deprecated
+ void waitForValidState(String waitForActivityVisible, int stackId)
+ throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(waitForActivityVisible)
+ .setStackId(stackId)
+ .build());
+ }
+
+ void waitForValidStateWithActivityType(String waitForActivityVisible, int activityType)
+ throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(waitForActivityVisible)
+ .setActivityType(activityType)
+ .build());
+ }
+
+ void waitForValidState(final ComponentName activityName, final int windowingMode,
+ final int activityType) throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(activityName)
+ .setActivityType(activityType)
+ .setWindowingMode(windowingMode)
+ .build());
+ }
+
+ @Deprecated
+ void waitForValidState(String waitForActivityVisible, int windowingMode, int activityType)
+ throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(waitForActivityVisible)
+ .setActivityType(activityType)
+ .setWindowingMode(windowingMode)
+ .build());
+ }
+
+ /**
+ * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
+ *
+ * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
+ * for equality.
+ * @param waitForActivitiesVisible array of activity state to wait for.
+ */
+ private void waitForValidState(boolean compareTaskAndStackBounds,
+ WaitForValidActivityState... waitForActivitiesVisible) throws Exception {
+ waitForValidState(compareTaskAndStackBounds, componentName, waitForActivitiesVisible);
+ }
+
+ /**
+ * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
+ *
+ * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds
+ * for equality.
+ * @param packageName name of the package of activities that we're waiting for.
+ * @param waitForActivitiesVisible array of activity states to wait for.
+ */
+ void waitForValidState(boolean compareTaskAndStackBounds, String packageName,
+ WaitForValidActivityState... waitForActivitiesVisible) throws Exception {
+ int retriesLeft = 5;
+ do {
+ // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
+ // requesting dump in some intermediate state.
+ mAmState.computeState();
+ mWmState.computeState();
+ if (shouldWaitForValidStacks(compareTaskAndStackBounds)
+ || shouldWaitForActivities(packageName, waitForActivitiesVisible)
+ || shouldWaitForWindows()) {
+ log("***Waiting for valid stacks and activities states...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+ }
+
+ /**
+ * Ensures all exiting windows have been removed.
+ */
+ void waitForAllExitingWindows() {
+ int retriesLeft = 5;
+ do {
+ mWmState.computeState();
+ if (mWmState.containsExitingWindow()) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertFalse(mWmState.containsExitingWindow());
+ }
+
+ void waitForAllStoppedActivities() throws Exception {
+ int retriesLeft = 5;
+ do {
+ mAmState.computeState();
+ if (mAmState.containsStartedActivities()){
+ log("***Waiting for valid stacks and activities states...");
+ try {
+ Thread.sleep(1500);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertFalse(mAmState.containsStartedActivities());
+ }
+
+ void waitForHomeActivityVisible() throws Exception {
+ ComponentName homeActivity = mAmState.getHomeActivityName();
+ // Sometimes this function is called before we know what Home Activity is
+ if (homeActivity == null) {
+ log("Computing state to determine Home Activity");
+ computeState();
+ homeActivity = mAmState.getHomeActivityName();
+ }
+ assertNotNull("homeActivity should not be null", homeActivity);
+ waitForValidState(new WaitForValidActivityState(homeActivity));
+ }
+
+ /**
+ * @return true if recents activity is visible. Devices without recents will return false
+ */
+ boolean waitForRecentsActivityVisible() throws Exception {
+ waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
+ "***Waiting for recents activity to be visible...");
+ return mAmState.isRecentsActivityVisible();
+ }
+
+ void waitForKeyguardShowingAndNotOccluded() throws Exception {
+ waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
+ && !state.getKeyguardControllerState().keyguardOccluded,
+ "***Waiting for Keyguard showing...");
+ }
+
+ void waitForKeyguardShowingAndOccluded() throws Exception {
+ waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
+ && state.getKeyguardControllerState().keyguardOccluded,
+ "***Waiting for Keyguard showing and occluded...");
+ }
+
+ void waitForKeyguardGone() throws Exception {
+ waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
+ "***Waiting for Keyguard gone...");
+ }
+
+ void waitForRotation(int rotation) throws Exception {
+ waitForWithWmState(state -> state.getRotation() == rotation,
+ "***Waiting for Rotation: " + rotation);
+ }
+
+ void waitForDisplayUnfrozen() throws Exception {
+ waitForWithWmState(state -> !state.isDisplayFrozen(),
+ "***Waiting for Display unfrozen");
+ }
+
+ void waitForActivityState(String activityName, String activityState)
+ throws Exception {
+ waitForWithAmState(state -> state.hasActivityState(activityName, activityState),
+ "***Waiting for Activity State: " + activityState);
+ }
+
+ @Deprecated
+ void waitForFocusedStack(int stackId) throws Exception {
+ waitForWithAmState(state -> state.getFocusedStackId() == stackId,
+ "***Waiting for focused stack...");
+ }
+
+ void waitForFocusedStack(int windowingMode, int activityType) throws Exception {
+ waitForWithAmState(state ->
+ (activityType == ACTIVITY_TYPE_UNDEFINED
+ || state.getFocusedStackActivityType() == activityType)
+ && (windowingMode == WINDOWING_MODE_UNDEFINED
+ || state.getFocusedStackWindowingMode() == windowingMode),
+ "***Waiting for focused stack...");
+ }
+
+ void waitForAppTransitionIdle() throws Exception {
+ waitForWithWmState(
+ state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
+ "***Waiting for app transition idle...");
+ }
+
+ void waitForWithAmState(Predicate<ActivityManagerState> waitCondition,
+ String message) throws Exception {
+ waitFor((amState, wmState) -> waitCondition.test(amState), message);
+ }
+
+ void waitForWithWmState(Predicate<WindowManagerState> waitCondition,
+ String message) throws Exception {
+ waitFor((amState, wmState) -> waitCondition.test(wmState), message);
+ }
+
+ void waitFor(
+ BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message)
+ throws Exception {
+ waitFor(message, () -> {
+ try {
+ mAmState.computeState();
+ mWmState.computeState();
+ } catch (Exception e) {
+ logE(e.toString());
+ return false;
+ }
+ return waitCondition.test(mAmState, mWmState);
+ });
+ }
+
+ void waitFor(String message, BooleanSupplier waitCondition) throws Exception {
+ int retriesLeft = 5;
+ do {
+ if (!waitCondition.getAsBoolean()) {
+ log(message);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+ }
+
+ /**
+ * @return true if should wait for valid stacks state.
+ */
+ private boolean shouldWaitForValidStacks(boolean compareTaskAndStackBounds) {
+ if (!taskListsInAmAndWmAreEqual()) {
+ // We want to wait for equal task lists in AM and WM in case we caught them in the
+ // middle of some state change operations.
+ log("***taskListsInAmAndWmAreEqual=false");
+ return true;
+ }
+ if (!stackBoundsInAMAndWMAreEqual()) {
+ // We want to wait a little for the stacks in AM and WM to have equal bounds as there
+ // might be a transition animation ongoing when we got the states from WM AM separately.
+ log("***stackBoundsInAMAndWMAreEqual=false");
+ return true;
+ }
+ try {
+ // Temporary fix to avoid catching intermediate state with different task bounds in AM
+ // and WM.
+ assertValidBounds(compareTaskAndStackBounds);
+ } catch (AssertionError e) {
+ log("***taskBoundsInAMAndWMAreEqual=false : " + e.getMessage());
+ return true;
+ }
+ final int stackCount = mAmState.getStackCount();
+ if (stackCount == 0) {
+ log("***stackCount=" + stackCount);
+ return true;
+ }
+ final int resumedActivitiesCount = mAmState.getResumedActivitiesCount();
+ if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount != 1) {
+ log("***resumedActivitiesCount=" + resumedActivitiesCount);
+ return true;
+ }
+ if (mAmState.getFocusedActivity() == null) {
+ log("***focusedActivity=null");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if should wait for some activities to become visible.
+ */
+ private boolean shouldWaitForActivities(String packageName,
+ WaitForValidActivityState... waitForActivitiesVisible) {
+ if (waitForActivitiesVisible == null || waitForActivitiesVisible.length == 0) {
+ return false;
+ }
+ // If the caller is interested in us waiting for some particular activity windows to be
+ // visible before compute the state. Check for the visibility of those activity windows
+ // and for placing them in correct stacks (if requested).
+ boolean allActivityWindowsVisible = true;
+ boolean tasksInCorrectStacks = true;
+ List<WindowState> matchingWindowStates = new ArrayList<>();
+ for (final WaitForValidActivityState state : waitForActivitiesVisible) {
+ final String activityComponentName;
+ final String windowName;
+ if (state.componentName != null) {
+ activityComponentName = state.componentName;
+ windowName = state.windowName;
+ } else {
+ final String activityName = state.activityName;
+ activityComponentName = (activityName != null)
+ ? ActivityManagerTestBase.getActivityComponentName(packageName, activityName)
+ : null;
+ // Check if window is visible - it should be represented as one of the window
+ // states.
+ windowName = (state.windowName != null) ? state.windowName
+ : ActivityManagerTestBase.getWindowName(packageName, activityName);
+ }
+ final int stackId = state.stackId;
+ final int windowingMode = state.windowingMode;
+ final int activityType = state.activityType;
+
+ mWmState.getMatchingVisibleWindowState(windowName, matchingWindowStates);
+ boolean activityWindowVisible = !matchingWindowStates.isEmpty();
+ if (!activityWindowVisible) {
+ log("Activity window not visible: " + windowName);
+ allActivityWindowsVisible = false;
+ } else if (activityComponentName != null
+ && !mAmState.isActivityVisible(activityComponentName)) {
+ log("Activity not visible: " + activityComponentName);
+ allActivityWindowsVisible = false;
+ } else {
+ // Check if window is already the correct state requested by test.
+ boolean windowInCorrectState = false;
+ for (WindowState ws : matchingWindowStates) {
+ if (stackId != INVALID_STACK_ID && ws.getStackId() != stackId) {
+ continue;
+ }
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && ws.getWindowingMode() != windowingMode) {
+ continue;
+ }
+ if (activityType != ACTIVITY_TYPE_UNDEFINED
+ && ws.getActivityType() != activityType) {
+ continue;
+ }
+ windowInCorrectState = true;
+ break;
+ }
+
+ if (!windowInCorrectState) {
+ log("Window in incorrect stack: " + state);
+ tasksInCorrectStacks = false;
+ }
+ }
+ }
+ return !allActivityWindowsVisible || !tasksInCorrectStacks;
+ }
+
+ /**
+ * @return true if should wait valid windows state.
+ */
+ private boolean shouldWaitForWindows() {
+ if (mWmState.getFrontWindow() == null) {
+ log("***frontWindow=null");
+ return true;
+ }
+ if (mWmState.getFocusedWindow() == null) {
+ log("***focusedWindow=null");
+ return true;
+ }
+ if (mWmState.getFocusedApp() == null) {
+ log("***focusedApp=null");
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean shouldWaitForDebuggerWindow() {
+ List<WindowState> matchingWindowStates = new ArrayList<>();
+ mWmState.getMatchingVisibleWindowState("android.server.am", matchingWindowStates);
+ for (WindowState ws : matchingWindowStates) {
+ if (ws.isDebuggerWindow()) {
+ return false;
+ }
+ }
+ log("Debugger window not available yet");
+ return true;
+ }
+
+ private boolean shouldWaitForActivityRecords(String[] waitForActivityRecords) {
+ if (waitForActivityRecords == null || waitForActivityRecords.length == 0) {
+ return false;
+ }
+ // Check if the activity records we're looking for is already added.
+ for (int i = 0; i < waitForActivityRecords.length; i++) {
+ if (!mAmState.isActivityVisible(waitForActivityRecords[i])) {
+ log("ActivityRecord " + waitForActivityRecords[i] + " not visible yet");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ActivityManagerState getAmState() {
+ return mAmState;
+ }
+
+ public WindowManagerState getWmState() {
+ return mWmState;
+ }
+
+ void assertSanity() throws Exception {
+ assertTrue("Must have stacks", mAmState.getStackCount() > 0);
+ if (!mAmState.getKeyguardControllerState().keyguardShowing) {
+ assertEquals("There should be one and only one resumed activity in the system.",
+ 1, mAmState.getResumedActivitiesCount());
+ }
+ assertNotNull("Must have focus activity.", mAmState.getFocusedActivity());
+
+ for (ActivityStack aStack : mAmState.getStacks()) {
+ final int stackId = aStack.mStackId;
+ for (ActivityTask aTask : aStack.getTasks()) {
+ assertEquals("Stack can only contain its own tasks", stackId, aTask.mStackId);
+ }
+ }
+
+ assertNotNull("Must have front window.", mWmState.getFrontWindow());
+ assertNotNull("Must have focused window.", mWmState.getFocusedWindow());
+ assertNotNull("Must have app.", mWmState.getFocusedApp());
+ }
+
+ void assertContainsStack(String msg, int windowingMode, int activityType) throws Exception {
+ assertTrue(msg, mAmState.containsStack(windowingMode, activityType));
+ assertTrue(msg, mWmState.containsStack(windowingMode, activityType));
+ }
+
+ @Deprecated
+ void assertDoesNotContainStack(String msg, int stackId) throws Exception {
+ assertFalse(msg, mAmState.containsStack(stackId));
+ assertFalse(msg, mWmState.containsStack(stackId));
+ }
+
+ void assertDoesNotContainStack(String msg, int windowingMode, int activityType)
+ throws Exception {
+ assertFalse(msg, mAmState.containsStack(windowingMode, activityType));
+ assertFalse(msg, mWmState.containsStack(windowingMode, activityType));
+ }
+
+ void assertFrontStack(String msg, int stackId) throws Exception {
+ assertEquals(msg, stackId, mAmState.getFrontStackId(DEFAULT_DISPLAY_ID));
+ assertEquals(msg, stackId, mWmState.getFrontStackId(DEFAULT_DISPLAY_ID));
+ }
+
+ void assertFrontStack(String msg, int windowingMode, int activityType)
+ throws Exception {
+ if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+ assertEquals(msg, windowingMode,
+ mAmState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID));
+ }
+ if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+ assertEquals(msg, activityType, mAmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+ }
+ }
+
+ void assertFrontStackActivityType(String msg, int activityType) throws Exception {
+ assertEquals(msg, activityType, mAmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+ assertEquals(msg, activityType, mWmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+ }
+
+ @Deprecated
+ void assertFocusedStack(String msg, int stackId) throws Exception {
+ assertEquals(msg, stackId, mAmState.getFocusedStackId());
+ }
+
+ void assertFocusedStack(String msg, int windowingMode, int activityType)
+ throws Exception {
+ if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+ assertEquals(msg, windowingMode, mAmState.getFocusedStackWindowingMode());
+ }
+ if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+ assertEquals(msg, activityType, mAmState.getFocusedStackActivityType());
+ }
+ }
+
+ void assertFocusedActivity(final String msg, final ComponentName activityName) {
+ final String activityComponentName = activityName.flattenToShortString();
+ assertEquals(msg, activityComponentName, mAmState.getFocusedActivity());
+ assertEquals(msg, activityComponentName, mWmState.getFocusedApp());
+ }
+
+ @Deprecated
+ void assertFocusedActivity(final String msg, final String activityName) {
+ final String activityComponentName =
+ ActivityManagerTestBase.getActivityComponentName(activityName);
+ assertEquals(msg, activityComponentName, mAmState.getFocusedActivity());
+ assertEquals(msg, activityComponentName, mWmState.getFocusedApp());
+ }
+
+ void assertNotFocusedActivity(String msg, String activityName) throws Exception {
+ final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ if (mAmState.getFocusedActivity().equals(componentName)) {
+ assertNotEquals(msg, mAmState.getFocusedActivity(), componentName);
+ }
+ if (mWmState.getFocusedApp().equals(componentName)) {
+ assertNotEquals(msg, mWmState.getFocusedApp(), componentName);
+ }
+ }
+
+ void assertResumedActivity(final String msg, final ComponentName activityName) {
+ assertEquals(msg, activityName.flattenToShortString(), mAmState.getResumedActivity());
+ }
+
+ @Deprecated
+ void assertResumedActivity(String msg, String activityName) throws Exception {
+ final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ assertEquals(msg, componentName, mAmState.getResumedActivity());
+ }
+
+ void assertNotResumedActivity(String msg, String activityName) throws Exception {
+ final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ if (mAmState.getResumedActivity().equals(componentName)) {
+ assertNotEquals(msg, mAmState.getResumedActivity(), componentName);
+ }
+ }
+
+ void assertFocusedWindow(String msg, String windowName) {
+ assertEquals(msg, windowName, mWmState.getFocusedWindow());
+ }
+
+ void assertNotFocusedWindow(String msg, String windowName) {
+ if (mWmState.getFocusedWindow().equals(windowName)) {
+ assertNotEquals(msg, mWmState.getFocusedWindow(), windowName);
+ }
+ }
+
+ void assertFrontWindow(String msg, String windowName) {
+ assertEquals(msg, windowName, mWmState.getFrontWindow());
+ }
+
+ @Deprecated
+ public void assertVisibility(String activityName, boolean visible) {
+ final String activityComponentName =
+ ActivityManagerTestBase.getActivityComponentName(activityName);
+ final String windowName =
+ ActivityManagerTestBase.getWindowName(activityName);
+ assertVisibility(activityComponentName, windowName, visible);
+ }
+
+ public void assertVisibility(final ComponentName activityName, final boolean visible) {
+ final String activityComponentName = activityName.flattenToShortString();
+ final String windowName = activityName.flattenToString();
+ assertVisibility(activityComponentName, windowName, visible);
+ }
+
+ private void assertVisibility(String activityComponentName, String windowName,
+ boolean visible) {
+ final boolean activityVisible = mAmState.isActivityVisible(activityComponentName);
+ final boolean windowVisible = mWmState.isWindowVisible(windowName);
+
+ if (visible) {
+ assertTrue("Activity=" + activityComponentName + " must be visible.", activityVisible);
+ assertTrue("Window=" + windowName + " must be visible.", windowVisible);
+ } else {
+ assertFalse("Activity=" + activityComponentName + " must NOT be visible.",
+ activityVisible);
+ assertFalse("Window=" + windowName + " must NOT be visible.", windowVisible);
+ }
+ }
+
+ void assertHomeActivityVisible(boolean visible) {
+ final ComponentName homeActivity = mAmState.getHomeActivityName();
+ assertNotNull(homeActivity);
+ assertVisibility(homeActivity, visible);
+ }
+
+ /**
+ * Asserts that the device default display minimim width is larger than the minimum task width.
+ */
+ void assertDeviceDefaultDisplaySize(String errorMessage) throws Exception {
+ computeState();
+ final int minTaskSizePx = defaultMinimalTaskSize(DEFAULT_DISPLAY_ID);
+ final Display display = getWmState().getDisplay(DEFAULT_DISPLAY_ID);
+ final Rect displayRect = display.getDisplayRect();
+ if (Math.min(displayRect.width(), displayRect.height()) < minTaskSizePx) {
+ fail(errorMessage);
+ }
+ }
+
+ public void assertKeyguardShowingAndOccluded() {
+ assertTrue(getAmState().getKeyguardControllerState().keyguardShowing);
+ assertTrue(getAmState().getKeyguardControllerState().keyguardOccluded);
+ }
+
+ public void assertKeyguardShowingAndNotOccluded() {
+ assertTrue(getAmState().getKeyguardControllerState().keyguardShowing);
+ assertFalse(getAmState().getKeyguardControllerState().keyguardOccluded);
+ }
+
+ public void assertKeyguardGone() {
+ assertFalse(getAmState().getKeyguardControllerState().keyguardShowing);
+ }
+
+ boolean taskListsInAmAndWmAreEqual() {
+ for (ActivityStack aStack : mAmState.getStacks()) {
+ final int stackId = aStack.mStackId;
+ final WindowStack wStack = mWmState.getStack(stackId);
+ if (wStack == null) {
+ log("Waiting for stack setup in WM, stackId=" + stackId);
+ return false;
+ }
+
+ for (ActivityTask aTask : aStack.getTasks()) {
+ if (wStack.getTask(aTask.mTaskId) == null) {
+ log("Task is in AM but not in WM, waiting for it to settle, taskId="
+ + aTask.mTaskId);
+ return false;
+ }
+ }
+
+ for (WindowTask wTask : wStack.mTasks) {
+ if (aStack.getTask(wTask.mTaskId) == null) {
+ log("Task is in WM but not in AM, waiting for it to settle, taskId="
+ + wTask.mTaskId);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /** Get the stack position on its display. */
+ int getStackIndexByActivityType(int activityType) {
+ int wmStackIndex = mWmState.getStackIndexByActivityType(activityType);
+ int amStackIndex = mAmState.getStackIndexByActivityType(activityType);
+ assertEquals("Window and activity manager must have the same stack position index",
+ amStackIndex, wmStackIndex);
+ return wmStackIndex;
+ }
+
+ boolean stackBoundsInAMAndWMAreEqual() {
+ for (ActivityStack aStack : mAmState.getStacks()) {
+ final int stackId = aStack.mStackId;
+ final WindowStack wStack = mWmState.getStack(stackId);
+ if (aStack.isFullscreen() != wStack.isFullscreen()) {
+ log("Waiting for correct fullscreen state, stackId=" + stackId);
+ return false;
+ }
+
+ final Rect aStackBounds = aStack.getBounds();
+ final Rect wStackBounds = wStack.getBounds();
+
+ if (aStack.isFullscreen()) {
+ if (aStackBounds != null) {
+ log("Waiting for correct stack state in AM, stackId=" + stackId);
+ return false;
+ }
+ } else if (!Objects.equals(aStackBounds, wStackBounds)) {
+ // If stack is not fullscreen - comparing bounds. Not doing it always because
+ // for fullscreen stack bounds in WM can be either null or equal to display size.
+ log("Waiting for stack bound equality in AM and WM, stackId=" + stackId);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check task bounds when docked to top/left.
+ */
+ void assertDockedTaskBounds(int taskWidth, int taskHeight, String activityName) {
+ // Task size can be affected by default minimal size.
+ int defaultMinimalTaskSize = defaultMinimalTaskSize(
+ mAmState.getStandardStackByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).mDisplayId);
+ int targetWidth = Math.max(taskWidth, defaultMinimalTaskSize);
+ int targetHeight = Math.max(taskHeight, defaultMinimalTaskSize);
+
+ assertEquals(new Rect(0, 0, targetWidth, targetHeight),
+ mAmState.getTaskByActivityName(activityName).getBounds());
+ }
+
+ void assertValidBounds(boolean compareTaskAndStackBounds) {
+ // Cycle through the stacks and tasks to figure out if the home stack is resizable
+ final ActivityTask homeTask = mAmState.getHomeTask();
+ final boolean homeStackIsResizable = homeTask != null
+ && homeTask.getResizeMode() == RESIZE_MODE_RESIZEABLE;
+
+ for (ActivityStack aStack : mAmState.getStacks()) {
+ final int stackId = aStack.mStackId;
+ final WindowStack wStack = mWmState.getStack(stackId);
+ assertNotNull("stackId=" + stackId + " in AM but not in WM?", wStack);
+
+ assertEquals("Stack fullscreen state in AM and WM must be equal stackId=" + stackId,
+ aStack.isFullscreen(), wStack.isFullscreen());
+
+ final Rect aStackBounds = aStack.getBounds();
+ final Rect wStackBounds = wStack.getBounds();
+
+ if (aStack.isFullscreen()) {
+ assertNull("Stack bounds in AM must be null stackId=" + stackId, aStackBounds);
+ } else {
+ assertEquals("Stack bounds in AM and WM must be equal stackId=" + stackId,
+ aStackBounds, wStackBounds);
+ }
+
+ for (ActivityTask aTask : aStack.getTasks()) {
+ final int taskId = aTask.mTaskId;
+ final WindowTask wTask = wStack.getTask(taskId);
+ assertNotNull(
+ "taskId=" + taskId + " in AM but not in WM? stackId=" + stackId, wTask);
+
+ final boolean aTaskIsFullscreen = aTask.isFullscreen();
+ final boolean wTaskIsFullscreen = wTask.isFullscreen();
+ assertEquals("Task fullscreen state in AM and WM must be equal taskId=" + taskId
+ + ", stackId=" + stackId, aTaskIsFullscreen, wTaskIsFullscreen);
+
+ final Rect aTaskBounds = aTask.getBounds();
+ final Rect wTaskBounds = wTask.getBounds();
+
+ if (aTaskIsFullscreen) {
+ assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId,
+ aTaskBounds);
+ } else if (!homeStackIsResizable && mWmState.isDockedStackMinimized()
+ && !isScreenPortrait(aStack.mDisplayId)) {
+ // When minimized using non-resizable launcher in landscape mode, it will move
+ // the task offscreen in the negative x direction unlike portrait that crops.
+ // The x value in the task bounds will not match the stack bounds since the
+ // only the task was moved.
+ assertEquals("Task bounds in AM and WM must match width taskId=" + taskId
+ + ", stackId" + stackId, aTaskBounds.width(),
+ wTaskBounds.width());
+ assertEquals("Task bounds in AM and WM must match height taskId=" + taskId
+ + ", stackId" + stackId, aTaskBounds.height(),
+ wTaskBounds.height());
+ assertEquals("Task bounds must match stack bounds y taskId=" + taskId
+ + ", stackId" + stackId, aTaskBounds.top,
+ wTaskBounds.top);
+ assertEquals("Task and stack bounds must match width taskId=" + taskId
+ + ", stackId" + stackId, aStackBounds.width(),
+ wTaskBounds.width());
+ assertEquals("Task and stack bounds must match height taskId=" + taskId
+ + ", stackId" + stackId, aStackBounds.height(),
+ wTaskBounds.height());
+ assertEquals("Task and stack bounds must match y taskId=" + taskId
+ + ", stackId" + stackId, aStackBounds.top,
+ wTaskBounds.top);
+ } else {
+ assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId
+ + ", stackId=" + stackId, aTaskBounds, wTaskBounds);
+
+ if (compareTaskAndStackBounds
+ && aStack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ int aTaskMinWidth = aTask.getMinWidth();
+ int aTaskMinHeight = aTask.getMinHeight();
+
+ if (aTaskMinWidth == -1 || aTaskMinHeight == -1) {
+ // Minimal dimension(s) not set for task - it should be using defaults.
+ int defaultMinimalSize =
+ aStack.getWindowingMode() == WINDOWING_MODE_PINNED
+ ? defaultMinimalPinnedTaskSize(aStack.mDisplayId)
+ : defaultMinimalTaskSize(aStack.mDisplayId);
+
+ if (aTaskMinWidth == -1) {
+ aTaskMinWidth = defaultMinimalSize;
+ }
+ if (aTaskMinHeight == -1) {
+ aTaskMinHeight = defaultMinimalSize;
+ }
+ }
+
+ if (aStackBounds.width() >= aTaskMinWidth
+ && aStackBounds.height() >= aTaskMinHeight
+ || aStack.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // Bounds are not smaller then minimal possible, so stack and task
+ // bounds must be equal.
+ assertEquals("Task bounds must be equal to stack bounds taskId="
+ + taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds);
+ } else if (aStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && homeStackIsResizable && mWmState.isDockedStackMinimized()) {
+ // Portrait if the display height is larger than the width
+ if (isScreenPortrait(aStack.mDisplayId)) {
+ assertEquals("Task width must be equal to stack width taskId="
+ + taskId + ", stackId=" + stackId,
+ aStackBounds.width(), wTaskBounds.width());
+ assertTrue("Task height must be greater than stack height "
+ + "taskId=" + taskId + ", stackId=" + stackId,
+ aStackBounds.height() < wTaskBounds.height());
+ assertEquals("Task and stack x position must be equal taskId="
+ + taskId + ", stackId=" + stackId,
+ wTaskBounds.left, wStackBounds.left);
+ } else {
+ assertTrue("Task width must be greater than stack width taskId="
+ + taskId + ", stackId=" + stackId,
+ aStackBounds.width() < wTaskBounds.width());
+ assertEquals("Task height must be equal to stack height taskId="
+ + taskId + ", stackId=" + stackId,
+ aStackBounds.height(), wTaskBounds.height());
+ assertEquals("Task and stack y position must be equal taskId="
+ + taskId + ", stackId=" + stackId, wTaskBounds.top,
+ wStackBounds.top);
+ }
+ } else {
+ // Minimal dimensions affect task size, so bounds of task and stack must
+ // be different - will compare dimensions instead.
+ int targetWidth = (int) Math.max(aTaskMinWidth,
+ aStackBounds.width());
+ assertEquals("Task width must be set according to minimal width"
+ + " taskId=" + taskId + ", stackId=" + stackId,
+ targetWidth, (int) wTaskBounds.width());
+ int targetHeight = (int) Math.max(aTaskMinHeight,
+ aStackBounds.height());
+ assertEquals("Task height must be set according to minimal height"
+ + " taskId=" + taskId + ", stackId=" + stackId,
+ targetHeight, (int) wTaskBounds.height());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ boolean isScreenPortrait() {
+ final int displayId = mAmState.getStandardStackByWindowingMode(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).mDisplayId;
+ return isScreenPortrait(displayId);
+ }
+
+ boolean isScreenPortrait(int displayId) {
+ final Rect displayRect = mWmState.getDisplay(displayId).getDisplayRect();
+ return displayRect.height() > displayRect.width();
+ }
+
+ static int dpToPx(float dp, int densityDpi) {
+ return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
+ }
+
+ private int defaultMinimalTaskSize(int displayId) {
+ return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
+ }
+
+ private int defaultMinimalPinnedTaskSize(int displayId) {
+ return dpToPx(DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
new file mode 100644
index 0000000..7be53c7
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ProtoExtractors.extract;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.server.am.proto.nano.ActivityDisplayProto;
+import com.android.server.am.proto.nano.ActivityRecordProto;
+import com.android.server.am.proto.nano.ActivityStackProto;
+import com.android.server.am.proto.nano.ActivityStackSupervisorProto;
+import com.android.server.am.proto.nano.KeyguardControllerProto;
+import com.android.server.am.proto.nano.TaskRecordProto;
+import com.android.server.wm.proto.nano.ConfigurationContainerProto;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+class ActivityManagerState {
+
+ public static final int DUMP_MODE_ACTIVITIES = 0;
+
+ public static final String STATE_RESUMED = "RESUMED";
+ public static final String STATE_PAUSED = "PAUSED";
+ public static final String STATE_STOPPED = "STOPPED";
+ public static final String STATE_DESTROYED = "DESTROYED";
+
+ private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity --proto activities";
+
+ // Displays in z-order with the top most at the front of the list, starting with primary.
+ private final List<ActivityDisplay> mDisplays = new ArrayList<>();
+ // Stacks in z-order with the top most at the front of the list, starting with primary display.
+ private final List<ActivityStack> mStacks = new ArrayList<>();
+ private KeyguardControllerState mKeyguardControllerState;
+ private int mFocusedStackId = -1;
+ private String mResumedActivityRecord = null;
+ private final List<String> mResumedActivities = new ArrayList<>();
+
+ void computeState() {
+ computeState(DUMP_MODE_ACTIVITIES);
+ }
+
+ void computeState(int dumpMode) {
+ // It is possible the system is in the middle of transition to the right state when we get
+ // the dump. We try a few times to get the information we need before giving up.
+ int retriesLeft = 3;
+ boolean retry = false;
+ byte[] dump = null;
+
+ log("==============================");
+ log(" ActivityManagerState ");
+ log("==============================");
+
+ do {
+ if (retry) {
+ log("***Incomplete AM state. Retrying...");
+ // Wait half a second between retries for activity manager to finish transitioning.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ }
+
+ String dumpsysCmd = "";
+ switch (dumpMode) {
+ case DUMP_MODE_ACTIVITIES:
+ dumpsysCmd = DUMPSYS_ACTIVITY_ACTIVITIES;
+ break;
+ }
+
+ dump = executeShellCommand(dumpsysCmd);
+ try {
+ parseSysDumpProto(dump);
+ } catch (InvalidProtocolBufferNanoException ex) {
+ throw new RuntimeException("Failed to parse dumpsys:\n"
+ + new String(dump, StandardCharsets.UTF_8), ex);
+ }
+
+ retry = mStacks.isEmpty() || mFocusedStackId == -1 || (mResumedActivityRecord == null
+ || mResumedActivities.isEmpty()) && !mKeyguardControllerState.keyguardShowing;
+ } while (retry && retriesLeft-- > 0);
+
+ if (mStacks.isEmpty()) {
+ logE("No stacks found...");
+ }
+ if (mFocusedStackId == -1) {
+ logE("No focused stack found...");
+ }
+ if (mResumedActivityRecord == null) {
+ logE("No focused activity found...");
+ }
+ if (mResumedActivities.isEmpty()) {
+ logE("No resumed activities found...");
+ }
+ }
+
+ private byte[] executeShellCommand(String cmd) {
+ try {
+ ParcelFileDescriptor pfd =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand(cmd);
+ byte[] buf = new byte[512];
+ int bytesRead;
+ FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+ while ((bytesRead = fis.read(buf)) != -1) {
+ stdout.write(buf, 0, bytesRead);
+ }
+ fis.close();
+ return stdout.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
+ reset();
+
+ ActivityStackSupervisorProto state = ActivityStackSupervisorProto.parseFrom(sysDump);
+ for (int i = 0; i < state.displays.length; i++) {
+ ActivityDisplayProto activityDisplay = state.displays[i];
+ mDisplays.add(new ActivityDisplay(activityDisplay, this));
+ }
+ mKeyguardControllerState = new KeyguardControllerState(state.keyguardController);
+ mFocusedStackId = state.focusedStackId;
+ if (state.resumedActivity != null) {
+ mResumedActivityRecord = state.resumedActivity.title;
+ }
+ }
+
+
+ private void reset() {
+ mDisplays.clear();
+ mStacks.clear();
+ mFocusedStackId = -1;
+ mResumedActivityRecord = null;
+ mResumedActivities.clear();
+ mKeyguardControllerState = null;
+ }
+
+ ActivityDisplay getDisplay(int displayId) {
+ for (ActivityDisplay display : mDisplays) {
+ if (display.mId == displayId) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ int getFrontStackId(int displayId) {
+ return getDisplay(displayId).mStacks.get(0).mStackId;
+ }
+
+ int getFrontStackActivityType(int displayId) {
+ return getDisplay(displayId).mStacks.get(0).getActivityType();
+ }
+
+ int getFrontStackWindowingMode(int displayId) {
+ return getDisplay(displayId).mStacks.get(0).getWindowingMode();
+ }
+
+ int getFocusedStackId() {
+ return mFocusedStackId;
+ }
+
+ int getFocusedStackActivityType() {
+ final ActivityStack stack = getStackById(mFocusedStackId);
+ return stack != null ? stack.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+ }
+
+ int getFocusedStackWindowingMode() {
+ final ActivityStack stack = getStackById(mFocusedStackId);
+ return stack != null ? stack.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
+ }
+
+ String getFocusedActivity() {
+ return mResumedActivityRecord;
+ }
+
+ String getResumedActivity() {
+ return mResumedActivities.get(0);
+ }
+
+ int getResumedActivitiesCount() {
+ return mResumedActivities.size();
+ }
+
+ public KeyguardControllerState getKeyguardControllerState() {
+ return mKeyguardControllerState;
+ }
+
+ boolean containsStack(int stackId) {
+ return getStackById(stackId) != null;
+ }
+
+ boolean containsStack(int windowingMode, int activityType) {
+ for (ActivityStack stack : mStacks) {
+ if (activityType != ACTIVITY_TYPE_UNDEFINED
+ && activityType != stack.getActivityType()) {
+ continue;
+ }
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && windowingMode != stack.getWindowingMode()) {
+ continue;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ ActivityStack getStackById(int stackId) {
+ for (ActivityStack stack : mStacks) {
+ if (stackId == stack.mStackId) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ ActivityStack getStackByActivityType(int activityType) {
+ for (ActivityStack stack : mStacks) {
+ if (activityType == stack.getActivityType()) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ ActivityStack getStandardStackByWindowingMode(int windowingMode) {
+ for (ActivityStack stack : mStacks) {
+ if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+ continue;
+ }
+ if (stack.getWindowingMode() == windowingMode) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ int getStandardTaskCountByWindowingMode(int windowingMode) {
+ int count = 0;
+ for (ActivityStack stack : mStacks) {
+ if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+ continue;
+ }
+ if (stack.getWindowingMode() == windowingMode) {
+ count += stack.mTasks.size();
+ }
+ }
+ return count;
+ }
+
+ /** Get the stack position on its display. */
+ int getStackIndexByActivityType(int activityType) {
+ for (ActivityDisplay display : mDisplays) {
+ for (int i = 0; i < display.mStacks.size(); i++) {
+ if (activityType == display.mStacks.get(i).getActivityType()) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /** Get the stack position on its display. */
+ int getStackIndexByActivityName(String activityName) {
+ final String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
+
+ for (ActivityDisplay display : mDisplays) {
+ for (int i = display.mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack stack = display.mStacks.get(i);
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(fullName)) {
+ return i;
+ }
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ List<ActivityDisplay> getDisplays() {
+ return new ArrayList<>(mDisplays);
+ }
+
+ List<ActivityStack> getStacks() {
+ return new ArrayList<>(mStacks);
+ }
+
+ int getStackCount() {
+ return mStacks.size();
+ }
+
+ boolean containsActivity(String activityName) {
+ for (ActivityStack stack : mStacks) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(activityName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean containsActivityInWindowingMode(String activityName, int windowingMode) {
+ final String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ for (ActivityStack stack : mStacks) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(fullName)
+ && activity.getWindowingMode() == windowingMode) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean isActivityVisible(String activityName) {
+ for (ActivityStack stack : mStacks) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(activityName)) {
+ return activity.visible;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean containsStartedActivities() {
+ for (ActivityStack stack : mStacks) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (!activity.state.equals(STATE_STOPPED)
+ && !activity.state.equals(STATE_DESTROYED)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean hasActivityState(String activityName, String activityState) {
+ String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ for (ActivityStack stack : mStacks) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(fullName)) {
+ return activity.state.equals(activityState);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ int getActivityProcId(String activityName) {
+ for (ActivityStack stack : mStacks) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(activityName)) {
+ return activity.procId;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ boolean isRecentsActivityVisible() {
+ final Activity recentsActivity = getRecentsActivity();
+ return recentsActivity != null && recentsActivity.visible;
+ }
+
+ ComponentName getHomeActivityName() {
+ Activity activity = getHomeActivity();
+ if (activity == null) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(activity.name);
+ }
+
+ ActivityTask getHomeTask() {
+ final ActivityStack homeStack = getStackByActivityType(ACTIVITY_TYPE_HOME);
+ if (homeStack != null && !homeStack.mTasks.isEmpty()) {
+ return homeStack.mTasks.get(0);
+ }
+ return null;
+ }
+
+ private ActivityTask getRecentsTask() {
+ final ActivityStack recentsStack = getStackByActivityType(ACTIVITY_TYPE_RECENTS);
+ if (recentsStack != null && !recentsStack.mTasks.isEmpty()) {
+ return recentsStack.mTasks.get(0);
+ }
+ return null;
+ }
+
+ private Activity getHomeActivity() {
+ final ActivityTask homeTask = getHomeTask();
+ return homeTask != null ? homeTask.mActivities.get(homeTask.mActivities.size() - 1) : null;
+ }
+
+ private Activity getRecentsActivity() {
+ final ActivityTask recentsTask = getRecentsTask();
+ return recentsTask != null ? recentsTask.mActivities.get(recentsTask.mActivities.size() - 1)
+ : null;
+ }
+
+ int getStackIdByActivityName(String activityName) {
+ final ActivityTask task = getTaskByActivityName(activityName);
+
+ if (task == null) {
+ return INVALID_STACK_ID;
+ }
+
+ return task.mStackId;
+ }
+
+ ActivityTask getTaskByActivityName(String activityName) {
+ return getTaskByActivityName(activityName, WINDOWING_MODE_UNDEFINED);
+ }
+
+ ActivityTask getTaskByActivityName(String activityName, int windowingMode) {
+ String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ for (ActivityStack stack : mStacks) {
+ if (windowingMode == WINDOWING_MODE_UNDEFINED
+ || windowingMode == stack.getWindowingMode()) {
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(fullName)) {
+ return task;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ static class ActivityDisplay extends ActivityContainer {
+
+ int mId;
+ ArrayList<ActivityStack> mStacks = new ArrayList<>();
+
+ ActivityDisplay(ActivityDisplayProto proto, ActivityManagerState amState) {
+ super(proto.configurationContainer);
+ mId = proto.id;
+ for (int i = 0; i < proto.stacks.length; i++) {
+ ActivityStack activityStack = new ActivityStack(proto.stacks[i]);
+ mStacks.add(activityStack);
+ // Also update activity manager state
+ amState.mStacks.add(activityStack);
+ if (activityStack.mResumedActivity != null) {
+ amState.mResumedActivities.add(activityStack.mResumedActivity);
+ }
+ }
+ }
+ }
+
+ static class ActivityStack extends ActivityContainer {
+
+ int mDisplayId;
+ int mStackId;
+ String mResumedActivity;
+ ArrayList<ActivityTask> mTasks = new ArrayList<>();
+
+ ActivityStack(ActivityStackProto proto) {
+ super(proto.configurationContainer);
+ mStackId = proto.id;
+ mDisplayId = proto.displayId;
+ mBounds = extract(proto.bounds);
+ mFullscreen = proto.fullscreen;
+ for (int i = 0; i < proto.tasks.length; i++) {
+ mTasks.add(new ActivityTask(proto.tasks[i]));
+ }
+ if (proto.resumedActivity != null) {
+ mResumedActivity = proto.resumedActivity.title;
+ }
+ }
+
+ /**
+ * @return the bottom task in the stack.
+ */
+ ActivityTask getBottomTask() {
+ if (!mTasks.isEmpty()) {
+ // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
+ // so the indices are inverted
+ return mTasks.get(mTasks.size() - 1);
+ }
+ return null;
+ }
+
+ /**
+ * @return the top task in the stack.
+ */
+ ActivityTask getTopTask() {
+ if (!mTasks.isEmpty()) {
+ // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
+ // so the indices are inverted
+ return mTasks.get(0);
+ }
+ return null;
+ }
+
+ List<ActivityTask> getTasks() {
+ return new ArrayList<>(mTasks);
+ }
+
+ ActivityTask getTask(int taskId) {
+ for (ActivityTask task : mTasks) {
+ if (taskId == task.mTaskId) {
+ return task;
+ }
+ }
+ return null;
+ }
+ }
+
+ static class ActivityTask extends ActivityContainer {
+
+ int mTaskId;
+ int mStackId;
+ Rect mLastNonFullscreenBounds;
+ String mRealActivity;
+ String mOrigActivity;
+ ArrayList<Activity> mActivities = new ArrayList<>();
+ int mTaskType = -1;
+ private int mResizeMode;
+
+ ActivityTask(TaskRecordProto proto) {
+ super(proto.configurationContainer);
+ mTaskId = proto.id;
+ mStackId = proto.stackId;
+ mLastNonFullscreenBounds = extract(proto.lastNonFullscreenBounds);
+ mRealActivity = proto.realActivity;
+ mOrigActivity = proto.origActivity;
+ mTaskType = proto.activityType;
+ mResizeMode = proto.resizeMode;
+ mFullscreen = proto.fullscreen;
+ mBounds = extract(proto.bounds);
+ mMinWidth = proto.minWidth;
+ mMinHeight = proto.minHeight;
+ for (int i = 0; i < proto.activities.length; i++) {
+ mActivities.add(new Activity(proto.activities[i]));
+ }
+ }
+
+ public int getResizeMode() {
+ return mResizeMode;
+ }
+
+ /**
+ * @return whether this task contains the given activity.
+ */
+ public boolean containsActivity(String activityName) {
+ for (Activity activity : mActivities) {
+ if (activity.name.equals(activityName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ static class Activity extends ActivityContainer {
+
+ String name;
+ String state;
+ boolean visible;
+ boolean frontOfTask;
+ int procId = -1;
+
+ Activity(ActivityRecordProto proto) {
+ super(proto.configurationContainer);
+ name = proto.identifier.title;
+ state = proto.state;
+ visible = proto.visible;
+ frontOfTask = proto.frontOfTask;
+ if (proto.procId != 0) {
+ procId = proto.procId;
+ }
+ }
+ }
+
+ static abstract class ActivityContainer extends WindowManagerState.ConfigurationContainer {
+ protected boolean mFullscreen;
+ protected Rect mBounds;
+ protected int mMinWidth = -1;
+ protected int mMinHeight = -1;
+
+ ActivityContainer(ConfigurationContainerProto proto) {
+ super(proto);
+ }
+
+ Rect getBounds() {
+ return mBounds;
+ }
+
+ boolean isFullscreen() {
+ return mFullscreen;
+ }
+
+ int getMinWidth() {
+ return mMinWidth;
+ }
+
+ int getMinHeight() {
+ return mMinHeight;
+ }
+ }
+
+ static class KeyguardControllerState {
+
+ boolean keyguardShowing = false;
+ boolean keyguardOccluded = false;
+
+ KeyguardControllerState(KeyguardControllerProto proto) {
+ if (proto != null) {
+ keyguardShowing = proto.keyguardShowing;
+ keyguardOccluded = proto.keyguardOccluded;
+ }
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
new file mode 100644
index 0000000..7fe707a
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -0,0 +1,1508 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
+import static android.content.pm.PackageManager.FEATURE_VR_MODE;
+import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+import static android.view.KeyEvent.KEYCODE_APP_SWITCH;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.ParcelFileDescriptor;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.view.Display;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class ActivityManagerTestBase {
+ private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
+ private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
+ private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
+
+ protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
+ ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
+ ACTIVITY_TYPE_UNDEFINED
+ };
+
+ private static final String TASK_ID_PREFIX = "taskId";
+
+ private static final String AM_STACK_LIST = "am stack list";
+
+ private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.am";
+ private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
+ = "am force-stop android.server.am.second";
+ private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
+ = "am force-stop android.server.am.third";
+
+ protected static final String AM_START_HOME_ACTIVITY_COMMAND =
+ "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
+
+ private static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT =
+ "am stack move-top-activity-to-pinned-stack %1d 0 0 500 500";
+
+ static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
+ static final String ALT_LAUNCHING_ACTIVITY = "AltLaunchingActivity";
+ static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
+
+ /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+ static final String FINISH_ACTIVITY_BROADCAST
+ = "am broadcast -a trigger_broadcast --ez finish true";
+
+ /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
+ static final String MOVE_TASK_TO_BACK_BROADCAST
+ = "am broadcast -a trigger_broadcast --ez moveToBack true";
+
+ private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
+ private static final String AM_RESIZE_STACK = "am stack resize ";
+
+ static final String AM_MOVE_TASK = "am stack move-task ";
+
+ private static final String AM_NO_HOME_SCREEN = "am no-home-screen";
+
+ private static final String LOCK_CREDENTIAL = "1234";
+
+ private static final int INVALID_DISPLAY_ID = Display.INVALID_DISPLAY;
+
+ private static final String DEFAULT_COMPONENT_NAME = "android.server.am";
+
+ private static final int UI_MODE_TYPE_MASK = 0x0f;
+ private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
+
+ private static Boolean sHasHomeScreen = null;
+
+ // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+ static String componentName = DEFAULT_COMPONENT_NAME;
+
+ protected static final int INVALID_DEVICE_ROTATION = -1;
+
+ protected Context mContext;
+ protected ActivityManager mAm;
+ protected UiDevice mDevice;
+
+ private boolean mLockCredentialsSet;
+
+ private boolean mLockDisabled;
+
+ @Deprecated
+ protected static String getAmStartCmd(final String activityName) {
+ return "am start -n " + getActivityComponentName(activityName);
+ }
+
+ /**
+ * @return the am command to start the given activity with the following extra key/value pairs.
+ * {@param keyValuePairs} must be a list of arguments defining each key/value extra.
+ */
+ // TODO: Make this more generic, for instance accepting flags or extras of other types.
+ protected static String getAmStartCmd(final ComponentName activityName,
+ final String... keyValuePairs) {
+ return getAmStartCmdInternal(activityName.flattenToShortString(), keyValuePairs);
+ }
+
+ @Deprecated
+ protected static String getAmStartCmd(final String activityName,
+ final String... keyValuePairs) {
+ return getAmStartCmdInternal(getActivityComponentName(activityName), keyValuePairs);
+ }
+
+ private static String getAmStartCmdInternal(final String activityName,
+ final String... keyValuePairs) {
+ final StringBuilder cmd = new StringBuilder("am start -n ").append(activityName);
+ if (keyValuePairs.length % 2 != 0) {
+ throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
+ }
+ for (int i = 0; i < keyValuePairs.length; i += 2) {
+ final String key = keyValuePairs[i];
+ final String value = keyValuePairs[i + 1];
+ cmd.append(String.format(" --es %s %s", key, value));
+ }
+ return cmd.toString();
+ }
+
+ protected static String getAmStartCmd(final String activityName, final int displayId) {
+ return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
+ + " --display " + displayId;
+ }
+
+ protected static String getAmStartCmdInNewTask(final String activityName) {
+ return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000";
+ }
+
+ protected static String getAmStartCmdOverHome(final String activityName) {
+ return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
+ }
+
+ protected static String getMoveToPinnedStackCommand(int stackId) {
+ return String.format(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT, stackId);
+ }
+
+ protected static String getOrientationBroadcast(int orientation) {
+ return "am broadcast -a trigger_broadcast --ei orientation " + orientation;
+ }
+
+ // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+ static String getActivityComponentName(final String activityName) {
+ return getActivityComponentName(componentName, activityName);
+ }
+
+ private static boolean isFullyQualifiedActivityName(String name) {
+ return name != null && name.contains(".");
+ }
+
+ static String getActivityComponentName(final String packageName, final String activityName) {
+ return packageName + "/" + (isFullyQualifiedActivityName(activityName) ? "" : ".") +
+ activityName;
+ }
+
+ // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+ // A little ugly, but lets avoid having to strip static everywhere for
+ // now.
+ public static void setComponentName(String name) {
+ componentName = name;
+ }
+
+ protected static void setDefaultComponentName() {
+ setComponentName(DEFAULT_COMPONENT_NAME);
+ }
+
+ protected static String getBaseWindowName() {
+ return getBaseWindowName(componentName);
+ }
+
+ static String getBaseWindowName(final String packageName) {
+ return getBaseWindowName(packageName, true /*prependPackageName*/);
+ }
+
+ static String getBaseWindowName(final String packageName, boolean prependPackageName) {
+ return packageName + "/" + (prependPackageName ? packageName + "." : "");
+ }
+
+ // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+ static String getWindowName(final String activityName) {
+ return getWindowName(componentName, activityName);
+ }
+
+ static String getWindowName(final String packageName, final String activityName) {
+ return getBaseWindowName(packageName, !isFullyQualifiedActivityName(activityName))
+ + activityName;
+ }
+
+ protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
+
+ private SurfaceTraceReceiver mSurfaceTraceReceiver;
+ private Thread mSurfaceTraceThread;
+
+ protected void installSurfaceObserver(SurfaceTraceReceiver.SurfaceObserver observer) {
+ mSurfaceTraceReceiver = new SurfaceTraceReceiver(observer);
+ mSurfaceTraceThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ registerSurfaceTraceReceiver("wm surface-trace", mSurfaceTraceReceiver);
+ } catch (IOException e) {
+ logE("Error running wm surface-trace: " + e.toString());
+ }
+ }
+ };
+ mSurfaceTraceThread.start();
+ }
+
+ protected void removeSurfaceObserver() {
+ mSurfaceTraceThread.interrupt();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mAm = mContext.getSystemService(ActivityManager.class);
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ setDefaultComponentName();
+ executeShellCommand("pm grant " + mContext.getPackageName()
+ + " android.permission.MANAGE_ACTIVITY_STACKS");
+ executeShellCommand("pm grant " + mContext.getPackageName()
+ + " android.permission.ACTIVITY_EMBEDDING");
+
+ wakeUpAndUnlockDevice();
+ pressHomeButton();
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ mLockCredentialsSet = false;
+ mLockDisabled = isLockDisabled();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ setLockDisabled(mLockDisabled);
+ executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
+ executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
+ executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
+ removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+ wakeUpAndUnlockDevice();
+ pressHomeButton();
+ }
+
+ protected void removeStacksWithActivityTypes(int... activityTypes) {
+ mAm.removeStacksWithActivityTypes(activityTypes);
+ }
+
+ protected void removeStacksInWindowingModes(int... windowingModes) {
+ mAm.removeStacksInWindowingModes(windowingModes);
+ waitForIdle();
+ }
+
+ public static String executeShellCommand(String command) {
+ log("Shell command: " + command);
+ try {
+ return SystemUtil
+ .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ } catch (IOException e) {
+ //bubble it up
+ logE("Error running shell command: " + command);
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected static void registerSurfaceTraceReceiver(String command, SurfaceTraceReceiver outputReceiver)
+ throws IOException {
+ log("Shell command: " + command);
+ ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand(command);
+ byte[] buf = new byte[512];
+ int bytesRead;
+ FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ while ((bytesRead = fis.read(buf)) != -1) {
+ outputReceiver.addOutput(buf, 0, bytesRead);
+ }
+ fis.close();
+ }
+
+ protected Bitmap takeScreenshot() throws Exception {
+ return InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
+ }
+
+ @Deprecated
+ protected void launchActivityInComponent(final String componentName,
+ final String targetActivityName, final String... keyValuePairs) throws Exception {
+ final String originalComponentName = ActivityManagerTestBase.componentName;
+ setComponentName(componentName);
+ launchActivity(targetActivityName, keyValuePairs);
+ setComponentName(originalComponentName);
+ }
+
+ protected void launchActivity(final ComponentName activityName, final String... keyValuePairs)
+ throws Exception {
+ executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
+ mAmWmState.waitForValidState(new WaitForValidActivityState(activityName));
+ }
+
+ @Deprecated
+ protected void launchActivity(final String targetActivityName, final String... keyValuePairs)
+ throws Exception {
+ executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+ mAmWmState.waitForValidState(targetActivityName);
+ }
+
+ protected void launchActivityNoWait(final ComponentName targetActivityName,
+ final String... keyValuePairs) throws Exception {
+ executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+ }
+
+ @Deprecated
+ protected void launchActivityNoWait(final String targetActivityName,
+ final String... keyValuePairs) throws Exception {
+ executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+ }
+
+ @Deprecated
+ protected void launchActivityInNewTask(final String targetActivityName) throws Exception {
+ executeShellCommand(getAmStartCmdInNewTask(targetActivityName));
+ mAmWmState.waitForValidState(targetActivityName);
+ }
+
+ /**
+ * Starts an activity in a new stack.
+ * @return the stack id of the newly created stack.
+ */
+ @Deprecated
+ protected int launchActivityInNewDynamicStack(final String activityName) throws Exception {
+ HashSet<Integer> stackIds = getStackIds();
+ executeShellCommand("am stack start " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
+ + " " + getActivityComponentName(activityName));
+ HashSet<Integer> newStackIds = getStackIds();
+ newStackIds.removeAll(stackIds);
+ if (newStackIds.isEmpty()) {
+ return INVALID_STACK_ID;
+ } else {
+ assertTrue(newStackIds.size() == 1);
+ return newStackIds.iterator().next();
+ }
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ /** Returns the set of stack ids. */
+ private HashSet<Integer> getStackIds() throws Exception {
+ mAmWmState.computeState();
+ final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
+ final HashSet<Integer> stackIds = new HashSet<>();
+ for (ActivityManagerState.ActivityStack s : stacks) {
+ stackIds.add(s.mStackId);
+ }
+ return stackIds;
+ }
+
+ protected void launchHomeActivity()
+ throws Exception {
+ executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
+ mAmWmState.waitForHomeActivityVisible();
+ }
+
+ protected void launchActivityOnDisplay(String targetActivityName, int displayId)
+ throws Exception {
+ executeShellCommand(getAmStartCmd(targetActivityName, displayId));
+
+ mAmWmState.waitForValidState(targetActivityName);
+ }
+
+ protected void launchActivity(String activityName, int windowingMode,
+ final String... keyValuePairs) throws Exception {
+ executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
+ + " --windowingMode " + windowingMode);
+ mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+ .setWindowingMode(windowingMode)
+ .build());
+ }
+
+ /**
+ * Launches {@param activityName} into split-screen primary windowing mode and also makes
+ * the recents activity visible to the side of it.
+ */
+ protected void launchActivityInSplitScreenWithRecents(String activityName) throws Exception {
+ launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+ }
+
+ protected void launchActivityInSplitScreenWithRecents(String activityName, int createMode)
+ throws Exception {
+ launchActivity(activityName);
+ final int taskId = mAmWmState.getAmState().getTaskByActivityName(activityName).mTaskId;
+ mAm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
+ false /* animate */, null /* initialBounds */, true /* showRecents */);
+
+ mAmWmState.waitForValidState(activityName,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.waitForRecentsActivityVisible();
+ }
+
+ /** @see #launchActivitiesInSplitScreen(LaunchActivityBuilder, LaunchActivityBuilder) */
+ protected void launchActivitiesInSplitScreen(String primaryActivity, String secondaryActivity)
+ throws Exception {
+ launchActivitiesInSplitScreen(
+ getLaunchActivityBuilder().setTargetActivityName(primaryActivity),
+ getLaunchActivityBuilder().setTargetActivityName(secondaryActivity));
+ }
+
+ /**
+ * Launches {@param primaryActivity} into split-screen primary windowing mode
+ * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
+ */
+ protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity,
+ LaunchActivityBuilder secondaryActivity) throws Exception {
+ // Launch split-screen primary.
+ String tmpLaunchingActivityName = primaryActivity.mLaunchingActivityName;
+ primaryActivity
+ // TODO(b/70618153): Work around issues with the activity launch builder where
+ // launching activity doesn't work. We don't really need launching activity in this
+ // case and should probably change activity launcher to work without a launching
+ // activity.
+ .setLaunchingActivityName(primaryActivity.mTargetActivityName)
+ .setWaitForLaunched(true)
+ .execute();
+ primaryActivity.setLaunchingActivityName(tmpLaunchingActivityName);
+
+ final int taskId = mAmWmState.getAmState().getTaskByActivityName(
+ primaryActivity.mTargetActivityName).mTaskId;
+ mAm.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+ true /* onTop */, false /* animate */, null /* initialBounds */,
+ true /* showRecents */);
+ mAmWmState.waitForRecentsActivityVisible();
+
+ // Launch split-screen secondary
+ tmpLaunchingActivityName = secondaryActivity.mLaunchingActivityName;
+ secondaryActivity
+ // TODO(b/70618153): Work around issues with the activity launch builder where
+ // launching activity doesn't work. We don't really need launching activity in this
+ // case and should probably change activity launcher to work without a launching
+ // activity.
+ .setLaunchingActivityName(secondaryActivity.mTargetActivityName)
+ .setWaitForLaunched(true)
+ .setToSide(true)
+ .execute();
+ secondaryActivity.setLaunchingActivityName(tmpLaunchingActivityName);
+ }
+
+ protected void setActivityTaskWindowingMode(final ComponentName activityName,
+ final int windowingMode) throws Exception {
+ final int taskId = getActivityTaskId(activityName);
+ mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+ mAmWmState.waitForValidState(activityName, windowingMode, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Deprecated
+ protected void setActivityTaskWindowingMode(String activityName, int windowingMode)
+ throws Exception {
+ final int taskId = getActivityTaskId(activityName);
+ mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+ mAmWmState.waitForValidState(activityName, windowingMode, ACTIVITY_TYPE_STANDARD);
+ }
+
+ protected void moveActivityToStack(String activityName, int stackId) throws Exception {
+ final int taskId = getActivityTaskId(activityName);
+ final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
+ executeShellCommand(cmd);
+
+ mAmWmState.waitForValidState(activityName, stackId);
+ }
+
+ protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
+ throws Exception {
+ final int taskId = getActivityTaskId(activityName);
+ final String cmd = "am task resize "
+ + taskId + " " + left + " " + top + " " + right + " " + bottom;
+ executeShellCommand(cmd);
+ }
+
+ protected void resizeDockedStack(
+ int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
+ executeShellCommand(AM_RESIZE_DOCKED_STACK
+ + "0 0 " + stackWidth + " " + stackHeight
+ + " 0 0 " + taskWidth + " " + taskHeight);
+ }
+
+ protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
+ int stackHeight) {
+ executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
+ stackTop, stackWidth, stackHeight));
+ }
+
+ protected void pressHomeButton() {
+ mDevice.pressHome();
+ }
+
+ protected void pressBackButton() {
+ mDevice.pressBack();
+ }
+
+ protected void pressAppSwitchButton() throws Exception {
+ mDevice.pressKeyCode(KEYCODE_APP_SWITCH);
+ mAmWmState.waitForRecentsActivityVisible();
+ mAmWmState.waitForAppTransitionIdle();
+ }
+
+ // Utility method for debugging, not used directly here, but useful, so kept around.
+ protected void printStacksAndTasks() {
+ String output = executeShellCommand(AM_STACK_LIST);
+ for (String line : output.split("\\n")) {
+ log(line);
+ }
+ }
+
+ @Deprecated
+ protected int getActivityTaskId(final ComponentName activityName) {
+ return getWindowTaskId(activityName.flattenToString());
+ }
+
+ @Deprecated
+ protected int getActivityTaskId(final String activityName) {
+ return getWindowTaskId(getWindowName(activityName));
+ }
+
+ @Deprecated
+ private int getWindowTaskId(final String windowName) {
+ final String output = executeShellCommand(AM_STACK_LIST);
+ final Pattern activityPattern = Pattern.compile("(.*) " + windowName + " (.*)");
+ for (final String line : output.split("\\n")) {
+ final Matcher matcher = activityPattern.matcher(line);
+ if (matcher.matches()) {
+ for (String word : line.split("\\s+")) {
+ if (word.startsWith(TASK_ID_PREFIX)) {
+ final String withColon = word.split("=")[1];
+ return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ protected boolean supportsVrMode() {
+ return hasDeviceFeature(FEATURE_VR_MODE)
+ && hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
+ }
+
+ protected boolean supportsPip() {
+ return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
+ || PRETEND_DEVICE_SUPPORTS_PIP;
+ }
+
+ protected boolean supportsFreeform() {
+ return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+ || PRETEND_DEVICE_SUPPORTS_FREEFORM;
+ }
+
+ protected boolean isHandheld() {
+ return !hasDeviceFeature(FEATURE_LEANBACK)
+ && !hasDeviceFeature(FEATURE_WATCH)
+ && !hasDeviceFeature(FEATURE_EMBEDDED);
+ }
+
+ protected boolean isTablet() {
+ // Larger than approx 7" tablets
+ return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ }
+
+ // TODO: Switch to using a feature flag, when available.
+ protected boolean isUiModeLockedToVrHeadset() {
+ final String output = runCommandAndPrintOutput("dumpsys uimode");
+
+ Integer curUiMode = null;
+ Boolean uiModeLocked = null;
+ for (String line : output.split("\\n")) {
+ line = line.trim();
+ Matcher matcher = sCurrentUiModePattern.matcher(line);
+ if (matcher.find()) {
+ curUiMode = Integer.parseInt(matcher.group(1), 16);
+ }
+ matcher = sUiModeLockedPattern.matcher(line);
+ if (matcher.find()) {
+ uiModeLocked = matcher.group(1).equals("true");
+ }
+ }
+
+ boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
+ && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
+
+ if (uiModeLockedToVrHeadset) {
+ log("UI mode is locked to VR headset");
+ }
+
+ return uiModeLockedToVrHeadset;
+ }
+
+ protected boolean supportsSplitScreenMultiWindow() {
+ return ActivityManager.supportsSplitScreenMultiWindow(mContext);
+ }
+
+ protected boolean hasHomeScreen() {
+ if (sHasHomeScreen == null) {
+ sHasHomeScreen = !executeShellCommand(AM_NO_HOME_SCREEN).startsWith("true");
+ }
+ return sHasHomeScreen;
+ }
+
+ /**
+ * Rotation support is indicated by explicitly having both landscape and portrait
+ * features or not listing either at all.
+ */
+ protected boolean supportsRotation() {
+ final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
+ final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
+ return (supportsLandscape && supportsPortrait)
+ || (!supportsLandscape && !supportsPortrait);
+ }
+
+ protected boolean hasDeviceFeature(final String requiredFeature) {
+ return InstrumentationRegistry.getContext()
+ .getPackageManager()
+ .hasSystemFeature(requiredFeature);
+ }
+
+ protected boolean isDisplayOn() {
+ String output = executeShellCommand("dumpsys power");
+ for (String line : output.split("\\n")) {
+ line = line.trim();
+
+ final Matcher matcher = sDisplayStatePattern.matcher(line);
+ if (matcher.matches()) {
+ final String state = matcher.group(1);
+ log("power state=" + state);
+ return "ON".equals(state);
+ }
+ }
+ log("power state :(");
+ return false;
+ }
+
+ protected void sleepDevice() {
+ int retriesLeft = 5;
+ runCommandAndPrintOutput("input keyevent SLEEP");
+ do {
+ if (isDisplayOn()) {
+ log("***Waiting for display to turn off...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+ }
+
+ protected void wakeUpAndUnlockDevice() {
+ wakeUpDevice();
+ unlockDevice();
+ }
+
+ protected void wakeUpAndRemoveLock() {
+ wakeUpDevice();
+ setLockDisabled(true);
+ }
+
+ protected void wakeUpDevice() {
+ runCommandAndPrintOutput("input keyevent WAKEUP");
+ }
+
+ protected void unlockDevice() {
+ mDevice.pressMenu();
+ }
+
+ protected void unlockDeviceWithCredential() throws Exception {
+ mDevice.pressMenu();
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ //ignored
+ }
+ enterAndConfirmLockCredential();
+ }
+
+ protected void enterAndConfirmLockCredential() throws Exception {
+ mDevice.waitForIdle(3000);
+
+ runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
+ mDevice.pressEnter();
+ }
+
+ protected void gotoKeyguard() throws Exception {
+ sleepDevice();
+ wakeUpDevice();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ }
+
+ protected void setLockCredential() {
+ mLockCredentialsSet = true;
+ runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
+ }
+
+ protected void removeLockCredential() {
+ runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
+ }
+
+ /**
+ * Returns whether the lock screen is disabled.
+ * @return true if the lock screen is disabled, false otherwise.
+ */
+ private boolean isLockDisabled() {
+ final String isLockDisabled = runCommandAndPrintOutput("locksettings get-disabled").trim();
+ if ("null".equals(isLockDisabled)) {
+ return false;
+ }
+ return Boolean.parseBoolean(isLockDisabled);
+
+ }
+
+ /**
+ * Disable the lock screen.
+ * @param lockDisabled true if should disable, false otherwise.
+ */
+ void setLockDisabled(boolean lockDisabled) {
+ runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
+ }
+
+ /** Helper class to save, set & wait, and restore rotation related preferences. */
+ protected class RotationSession extends SettingsSession<Integer> {
+ private final SettingsSession<Integer> mUserRotation;
+
+ RotationSession() throws Exception {
+ // Save accelerometer_rotation preference.
+ super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+ Settings.System::getInt, Settings.System::putInt);
+ mUserRotation = new SettingsSession<>(
+ Settings.System.getUriFor(Settings.System.USER_ROTATION),
+ Settings.System::getInt, Settings.System::putInt);
+ // Disable accelerometer_rotation.
+ super.set(0);
+ }
+
+ @Override
+ public void set(@NonNull Integer value) throws Exception {
+ mUserRotation.set(value);
+ // Wait for settling rotation.
+ mAmWmState.waitForRotation(value);
+ }
+
+ @Override
+ public void close() throws Exception {
+ mUserRotation.close();
+ // Restore accelerometer_rotation preference.
+ super.close();
+ }
+ }
+
+ protected int getDeviceRotation(int displayId) {
+ final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
+ Pattern pattern = Pattern.compile(
+ "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)"
+ + "(rotation)(\\s+)(\\d+)");
+ Matcher matcher = pattern.matcher(displays);
+ if (matcher.find()) {
+ final String match = matcher.group(7);
+ return Integer.parseInt(match);
+ }
+
+ return INVALID_DEVICE_ROTATION;
+ }
+
+ protected String runCommandAndPrintOutput(String command) {
+ final String output = executeShellCommand(command);
+ log(output);
+ return output;
+ }
+
+ /**
+ * Tries to clear logcat and inserts log separator in case clearing didn't succeed, so we can
+ * always find the starting point from where to evaluate following logs.
+ * @return Unique log separator.
+ */
+ protected String clearLogcat() {
+ executeShellCommand("logcat -c");
+ final String uniqueString = UUID.randomUUID().toString();
+ executeShellCommand("log -t " + LOG_SEPARATOR + " " + uniqueString);
+ return uniqueString;
+ }
+
+ void assertActivityLifecycle(String activityName, boolean relaunched,
+ String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = verifyLifecycleCondition(activityName, logSeparator, relaunched);
+ if (resultString != null) {
+ log("***Waiting for valid lifecycle state: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ /** @return Error string if lifecycle counts don't match, null if everything is fine. */
+ private String verifyLifecycleCondition(String activityName, String logSeparator,
+ boolean relaunched) {
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+ logSeparator);
+ if (relaunched) {
+ if (lifecycleCounts.mDestroyCount < 1) {
+ return activityName + " must have been destroyed. mDestroyCount="
+ + lifecycleCounts.mDestroyCount;
+ }
+ if (lifecycleCounts.mCreateCount < 1) {
+ return activityName + " must have been (re)created. mCreateCount="
+ + lifecycleCounts.mCreateCount;
+ }
+ } else {
+ if (lifecycleCounts.mDestroyCount > 0) {
+ return activityName + " must *NOT* have been destroyed. mDestroyCount="
+ + lifecycleCounts.mDestroyCount;
+ }
+ if (lifecycleCounts.mCreateCount > 0) {
+ return activityName + " must *NOT* have been (re)created. mCreateCount="
+ + lifecycleCounts.mCreateCount;
+ }
+ if (lifecycleCounts.mConfigurationChangedCount < 1) {
+ return activityName + " must have received configuration changed. "
+ + "mConfigurationChangedCount="
+ + lifecycleCounts.mConfigurationChangedCount;
+ }
+ }
+ return null;
+ }
+
+ protected void assertRelaunchOrConfigChanged(
+ String activityName, int numRelaunch, int numConfigChange, String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = verifyRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
+ logSeparator);
+ if (resultString != null) {
+ log("***Waiting for relaunch or config changed: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ /** @return Error string if lifecycle counts don't match, null if everything is fine. */
+ private String verifyRelaunchOrConfigChanged(String activityName, int numRelaunch,
+ int numConfigChange, String logSeparator) {
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+ logSeparator);
+
+ if (lifecycleCounts.mDestroyCount != numRelaunch) {
+ return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
+ + " time(s), expecting " + numRelaunch;
+ } else if (lifecycleCounts.mCreateCount != numRelaunch) {
+ return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
+ + " time(s), expecting " + numRelaunch;
+ } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
+ return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
+ + " onConfigurationChanged() calls, expecting " + numConfigChange;
+ }
+ return null;
+ }
+
+ protected void assertActivityDestroyed(String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = verifyActivityDestroyed(activityName, logSeparator);
+ if (resultString != null) {
+ log("***Waiting for activity destroyed: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ /** @return Error string if lifecycle counts don't match, null if everything is fine. */
+ private String verifyActivityDestroyed(String activityName, String logSeparator) {
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+ logSeparator);
+
+ if (lifecycleCounts.mDestroyCount != 1) {
+ return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
+ + " time(s), expecting single destruction.";
+ } else if (lifecycleCounts.mCreateCount != 0) {
+ return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
+ + " time(s), not expecting any.";
+ } else if (lifecycleCounts.mConfigurationChangedCount != 0) {
+ return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
+ + " onConfigurationChanged() calls, not expecting any.";
+ }
+ return null;
+ }
+
+ protected static String[] getDeviceLogsForComponent(String componentName, String logSeparator) {
+ return getDeviceLogsForComponents(new String[]{componentName}, logSeparator);
+ }
+
+ protected static String[] getDeviceLogsForComponents(final String[] componentNames,
+ String logSeparator) {
+ String filters = LOG_SEPARATOR + ":I ";
+ for (String component : componentNames) {
+ filters += component + ":I ";
+ }
+ final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S")
+ .split("\\n");
+ if (logSeparator == null) {
+ return result;
+ }
+
+ // Make sure that we only check logs after the separator.
+ int i = 0;
+ boolean lookingForSeparator = true;
+ while (i < result.length && lookingForSeparator) {
+ if (result[i].contains(logSeparator)) {
+ lookingForSeparator = false;
+ }
+ i++;
+ }
+ final String[] filteredResult = new String[result.length - i];
+ for (int curPos = 0; i < result.length; curPos++, i++) {
+ filteredResult[curPos] = result[i];
+ }
+ return filteredResult;
+ }
+
+ void assertSingleLaunch(String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = validateLifecycleCounts(activityName, logSeparator, 1 /* createCount */,
+ 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
+ 0 /* destroyCount */);
+ if (resultString != null) {
+ log("***Waiting for valid lifecycle state: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ public void assertSingleLaunchAndStop(String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = validateLifecycleCounts(activityName, logSeparator, 1 /* createCount */,
+ 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
+ 0 /* destroyCount */);
+ if (resultString != null) {
+ log("***Waiting for valid lifecycle state: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ public void assertSingleStartAndStop(String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = validateLifecycleCounts(activityName, logSeparator, 0 /* createCount */,
+ 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
+ 0 /* destroyCount */);
+ if (resultString != null) {
+ log("***Waiting for valid lifecycle state: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ void assertSingleStart(String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ String resultString;
+ do {
+ resultString = validateLifecycleCounts(activityName, logSeparator, 0 /* createCount */,
+ 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
+ 0 /* destroyCount */);
+ if (resultString != null) {
+ log("***Waiting for valid lifecycle state: " + resultString);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+
+ assertNull(resultString, resultString);
+ }
+
+ private String validateLifecycleCounts(String activityName, String logSeparator,
+ int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
+ int destroyCount) {
+
+ final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
+ logSeparator);
+
+ if (lifecycleCounts.mCreateCount != createCount) {
+ return activityName + " created " + lifecycleCounts.mCreateCount + " times.";
+ }
+ if (lifecycleCounts.mStartCount != startCount) {
+ return activityName + " started " + lifecycleCounts.mStartCount + " times.";
+ }
+ if (lifecycleCounts.mResumeCount != resumeCount) {
+ return activityName + " resumed " + lifecycleCounts.mResumeCount + " times.";
+ }
+ if (lifecycleCounts.mPauseCount != pauseCount) {
+ return activityName + " paused " + lifecycleCounts.mPauseCount + " times.";
+ }
+ if (lifecycleCounts.mStopCount != stopCount) {
+ return activityName + " stopped " + lifecycleCounts.mStopCount + " times.";
+ }
+ if (lifecycleCounts.mDestroyCount != destroyCount) {
+ return activityName + " destroyed " + lifecycleCounts.mDestroyCount + " times.";
+ }
+ return null;
+ }
+
+ // TODO: Now that our test are device side, we can convert these to a more direct communication
+ // channel vs. depending on logs.
+ private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
+ private static final Pattern sStartPattern = Pattern.compile("(.+): onStart");
+ private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
+ private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
+ private static final Pattern sConfigurationChangedPattern =
+ Pattern.compile("(.+): onConfigurationChanged");
+ private static final Pattern sMovedToDisplayPattern =
+ Pattern.compile("(.+): onMovedToDisplay");
+ private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
+ private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
+ private static final Pattern sMultiWindowModeChangedPattern =
+ Pattern.compile("(.+): onMultiWindowModeChanged");
+ private static final Pattern sPictureInPictureModeChangedPattern =
+ Pattern.compile("(.+): onPictureInPictureModeChanged");
+ private static final Pattern sUserLeaveHintPattern = Pattern.compile("(.+): onUserLeaveHint");
+ private static final Pattern sNewConfigPattern = Pattern.compile(
+ "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
+ + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
+ + " orientation=(\\d+)");
+ private static final Pattern sDisplayStatePattern =
+ Pattern.compile("Display Power: state=(.+)");
+ private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
+ private static final Pattern sUiModeLockedPattern =
+ Pattern.compile("mUiModeLocked=(true|false)");
+
+ static class ReportedSizes {
+ int widthDp;
+ int heightDp;
+ int displayWidth;
+ int displayHeight;
+ int metricsWidth;
+ int metricsHeight;
+ int smallestWidthDp;
+ int densityDpi;
+ int orientation;
+
+ @Override
+ public String toString() {
+ return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
+ + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
+ + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
+ + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
+ + " orientation=" + orientation + "}";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ( this == obj ) return true;
+ if ( !(obj instanceof ReportedSizes) ) return false;
+ ReportedSizes that = (ReportedSizes) obj;
+ return widthDp == that.widthDp
+ && heightDp == that.heightDp
+ && displayWidth == that.displayWidth
+ && displayHeight == that.displayHeight
+ && metricsWidth == that.metricsWidth
+ && metricsHeight == that.metricsHeight
+ && smallestWidthDp == that.smallestWidthDp
+ && densityDpi == that.densityDpi
+ && orientation == that.orientation;
+ }
+ }
+
+ ReportedSizes getLastReportedSizesForActivity(String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ ReportedSizes result;
+ do {
+ result = readLastReportedSizes(activityName, logSeparator);
+ if (result == null) {
+ log("***Waiting for sizes to be reported...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+ return result;
+ }
+
+ private ReportedSizes readLastReportedSizes(String activityName, String logSeparator) {
+ final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
+ for (int i = lines.length - 1; i >= 0; i--) {
+ final String line = lines[i].trim();
+ final Matcher matcher = sNewConfigPattern.matcher(line);
+ if (matcher.matches()) {
+ ReportedSizes details = new ReportedSizes();
+ details.widthDp = Integer.parseInt(matcher.group(2));
+ details.heightDp = Integer.parseInt(matcher.group(3));
+ details.displayWidth = Integer.parseInt(matcher.group(4));
+ details.displayHeight = Integer.parseInt(matcher.group(5));
+ details.metricsWidth = Integer.parseInt(matcher.group(6));
+ details.metricsHeight = Integer.parseInt(matcher.group(7));
+ details.smallestWidthDp = Integer.parseInt(matcher.group(8));
+ details.densityDpi = Integer.parseInt(matcher.group(9));
+ details.orientation = Integer.parseInt(matcher.group(10));
+ return details;
+ }
+ }
+ return null;
+ }
+
+ /** Waits for at least one onMultiWindowModeChanged event. */
+ ActivityLifecycleCounts waitForOnMultiWindowModeChanged(
+ String activityName, String logSeparator) {
+ int retriesLeft = 5;
+ ActivityLifecycleCounts result;
+ do {
+ result = new ActivityLifecycleCounts(activityName, logSeparator);
+ if (result.mMultiWindowModeChangedCount < 1) {
+ log("***waitForOnMultiWindowModeChanged...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ } else {
+ break;
+ }
+ } while (retriesLeft-- > 0);
+ return result;
+
+ }
+
+ // TODO: Now that our test are device side, we can convert these to a more direct communication
+ // channel vs. depending on logs.
+ static class ActivityLifecycleCounts {
+ int mCreateCount;
+ int mStartCount;
+ int mResumeCount;
+ int mConfigurationChangedCount;
+ int mLastConfigurationChangedLineIndex;
+ int mMovedToDisplayCount;
+ int mMultiWindowModeChangedCount;
+ int mLastMultiWindowModeChangedLineIndex;
+ int mPictureInPictureModeChangedCount;
+ int mLastPictureInPictureModeChangedLineIndex;
+ int mUserLeaveHintCount;
+ int mPauseCount;
+ int mStopCount;
+ int mLastStopLineIndex;
+ int mDestroyCount;
+
+ ActivityLifecycleCounts(String activityName, String logSeparator) {
+ int lineIndex = 0;
+ waitForIdle();
+ for (String line : getDeviceLogsForComponent(activityName, logSeparator)) {
+ line = line.trim();
+ lineIndex++;
+
+ Matcher matcher = sCreatePattern.matcher(line);
+ if (matcher.matches()) {
+ mCreateCount++;
+ continue;
+ }
+
+ matcher = sStartPattern.matcher(line);
+ if (matcher.matches()) {
+ mStartCount++;
+ continue;
+ }
+
+ matcher = sResumePattern.matcher(line);
+ if (matcher.matches()) {
+ mResumeCount++;
+ continue;
+ }
+
+ matcher = sConfigurationChangedPattern.matcher(line);
+ if (matcher.matches()) {
+ mConfigurationChangedCount++;
+ mLastConfigurationChangedLineIndex = lineIndex;
+ continue;
+ }
+
+ matcher = sMovedToDisplayPattern.matcher(line);
+ if (matcher.matches()) {
+ mMovedToDisplayCount++;
+ continue;
+ }
+
+ matcher = sMultiWindowModeChangedPattern.matcher(line);
+ if (matcher.matches()) {
+ mMultiWindowModeChangedCount++;
+ mLastMultiWindowModeChangedLineIndex = lineIndex;
+ continue;
+ }
+
+ matcher = sPictureInPictureModeChangedPattern.matcher(line);
+ if (matcher.matches()) {
+ mPictureInPictureModeChangedCount++;
+ mLastPictureInPictureModeChangedLineIndex = lineIndex;
+ continue;
+ }
+
+ matcher = sUserLeaveHintPattern.matcher(line);
+ if (matcher.matches()) {
+ mUserLeaveHintCount++;
+ continue;
+ }
+
+ matcher = sPausePattern.matcher(line);
+ if (matcher.matches()) {
+ mPauseCount++;
+ continue;
+ }
+
+ matcher = sStopPattern.matcher(line);
+ if (matcher.matches()) {
+ mStopCount++;
+ mLastStopLineIndex = lineIndex;
+ continue;
+ }
+
+ matcher = sDestroyPattern.matcher(line);
+ if (matcher.matches()) {
+ mDestroyCount++;
+ continue;
+ }
+ }
+ }
+ }
+
+ protected void stopTestPackage(final ComponentName activityName) throws Exception {
+ executeShellCommand("am force-stop " + activityName.getPackageName());
+ }
+
+ protected LaunchActivityBuilder getLaunchActivityBuilder() {
+ return new LaunchActivityBuilder(mAmWmState);
+ }
+
+ protected static class LaunchActivityBuilder {
+ private final ActivityAndWindowManagersState mAmWmState;
+
+ // The activity to be launched
+ private String mTargetActivityName = "TestActivity";
+ private String mTargetPackage = componentName;
+ private boolean mToSide;
+ private boolean mRandomData;
+ private boolean mNewTask;
+ private boolean mMultipleTask;
+ private int mDisplayId = INVALID_DISPLAY_ID;
+ // A proxy activity that launches other activities including mTargetActivityName
+ private String mLaunchingActivityName = LAUNCHING_ACTIVITY;
+ private boolean mReorderToFront;
+ private boolean mWaitForLaunched;
+ // Use of the following variables indicates that a broadcast receiver should be used instead
+ // of a launching activity;
+ private String mBroadcastReceiverComponent;
+ private String mBroadcastReceiverAction;
+
+ public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) {
+ mAmWmState = amWmState;
+ mWaitForLaunched = true;
+ }
+
+ public LaunchActivityBuilder setToSide(boolean toSide) {
+ mToSide = toSide;
+ return this;
+ }
+
+ public LaunchActivityBuilder setRandomData(boolean randomData) {
+ mRandomData = randomData;
+ return this;
+ }
+
+ public LaunchActivityBuilder setNewTask(boolean newTask) {
+ mNewTask = newTask;
+ return this;
+ }
+
+ public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
+ mMultipleTask = multipleTask;
+ return this;
+ }
+
+ public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
+ mReorderToFront = reorderToFront;
+ return this;
+ }
+
+ public LaunchActivityBuilder setTargetActivity(ComponentName activity) {
+ mTargetActivityName = activity.getShortClassName();
+ mTargetPackage = activity.getPackageName();
+ return this;
+ }
+
+ public LaunchActivityBuilder setTargetActivityName(String name) {
+ mTargetActivityName = name;
+ return this;
+ }
+
+ public LaunchActivityBuilder setTargetPackage(String pkg) {
+ mTargetPackage = pkg;
+ return this;
+ }
+
+ public LaunchActivityBuilder setDisplayId(int id) {
+ mDisplayId = id;
+ return this;
+ }
+
+ public LaunchActivityBuilder setLaunchingActivityName(String name) {
+ mLaunchingActivityName = name;
+ return this;
+ }
+
+ public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
+ mWaitForLaunched = shouldWait;
+ return this;
+ }
+
+ /** Use broadcast receiver instead of launching activity. */
+ public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
+ final String broadcastAction) {
+ mBroadcastReceiverComponent = broadcastReceiver.flattenToShortString();
+ mBroadcastReceiverAction = broadcastAction;
+ return this;
+ }
+
+ /** Use {@link #setUseBroadcastReceiver(ComponentName, String)} instead. */
+ @Deprecated
+ public LaunchActivityBuilder setUseBroadcastReceiver(String componentName,
+ String broadcastAction) {
+ mBroadcastReceiverComponent = componentName;
+ mBroadcastReceiverAction = broadcastAction;
+ return this;
+ }
+
+ public void execute() throws Exception {
+ StringBuilder commandBuilder = new StringBuilder();
+ if (mBroadcastReceiverComponent != null && mBroadcastReceiverAction != null) {
+ // Use broadcast receiver to launch the target.
+ commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction);
+ commandBuilder.append(" -p ").append(mBroadcastReceiverComponent);
+ } else {
+ // Use launching activity to launch the target.
+ commandBuilder.append(getAmStartCmd(mLaunchingActivityName));
+ commandBuilder.append(" -f 0x20000000");
+ }
+
+ // Add a flag to ensure we actually mean to launch an activity.
+ commandBuilder.append(" --ez launch_activity true");
+
+ if (mToSide) {
+ commandBuilder.append(" --ez launch_to_the_side true");
+ }
+ if (mRandomData) {
+ commandBuilder.append(" --ez random_data true");
+ }
+ if (mNewTask) {
+ commandBuilder.append(" --ez new_task true");
+ }
+ if (mMultipleTask) {
+ commandBuilder.append(" --ez multiple_task true");
+ }
+ if (mReorderToFront) {
+ commandBuilder.append(" --ez reorder_to_front true");
+ }
+ if (mTargetActivityName != null) {
+ commandBuilder.append(" --es target_activity ").append(mTargetActivityName);
+ commandBuilder.append(" --es package_name ").append(mTargetPackage);
+ }
+ if (mDisplayId != INVALID_DISPLAY_ID) {
+ commandBuilder.append(" --ei display_id ").append(mDisplayId);
+ }
+ executeShellCommand(commandBuilder.toString());
+
+ if (mWaitForLaunched) {
+ mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, mTargetPackage,
+ new WaitForValidActivityState.Builder(mTargetActivityName).build());
+ }
+ }
+ }
+
+ protected void tearDownLockCredentials() throws Exception {
+ if (!mLockCredentialsSet) {
+ return;
+ }
+
+ removeLockCredential();
+ // Dismiss active keyguard after credential is cleared, so
+ // keyguard doesn't ask for the stale credential.
+ pressBackButton();
+ sleepDevice();
+ wakeUpAndUnlockDevice();
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ProtoExtractors.java b/tests/framework/base/activitymanager/util/src/android/server/am/ProtoExtractors.java
new file mode 100644
index 0000000..060fc60
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ProtoExtractors.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.app.WindowConfiguration;
+import android.app.nano.WindowConfigurationProto;
+import android.content.nano.ConfigurationProto;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.nano.RectProto;
+
+/**
+ * Utility class for extracting some common framework object from nano proto objects.
+ * Normally the extractors will be in the framework object class, but we don't want the framework to
+ * depend on nano proto due to size cost.
+ * TODO: This class should probably be in frameworks/base/lib project so it can be used
+ * outside of CTS.
+ */
+public class ProtoExtractors {
+ public static Configuration extract(ConfigurationProto proto) {
+ final Configuration config = new Configuration();
+ if (proto == null) {
+ return config;
+ }
+ config.windowConfiguration.setTo(extract(proto.windowConfiguration));
+ config.densityDpi = proto.densityDpi;
+ config.orientation = proto.orientation;
+ config.screenHeightDp = proto.screenHeightDp;
+ config.screenWidthDp = proto.screenWidthDp;
+ config.smallestScreenWidthDp = proto.smallestScreenWidthDp;
+ config.screenLayout = proto.screenLayout;
+ config.uiMode = proto.uiMode;
+ return config;
+ }
+
+ public static WindowConfiguration extract(WindowConfigurationProto proto) {
+ final WindowConfiguration config = new WindowConfiguration();
+ if (proto == null) {
+ return config;
+ }
+ config.setAppBounds(extract(proto.appBounds));
+ config.setWindowingMode(proto.windowingMode);
+ config.setActivityType(proto.activityType);
+ return config;
+ }
+
+ public static Rect extract(RectProto proto) {
+ if (proto == null) {
+ return null;
+ }
+ return new Rect(proto.left, proto.top, proto.right, proto.bottom);
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java b/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java
new file mode 100644
index 0000000..e3ad3be
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.util.Log;
+
+/**
+ * Util class to perform simple state logging.
+ */
+public class StateLogger {
+
+ private static final boolean DEBUG = false;
+ private static String TAG = "AMWM";
+
+ /**
+ * Simple info-level logging gated by {@link #DEBUG} flag
+ */
+ public static void log(String logText) {
+ if (DEBUG) {
+ Log.i(TAG, logText);
+ }
+ }
+
+ public static void logAlways(String logText) {
+ Log.i(TAG, logText);
+ }
+
+ public static void logE(String logText) {
+ Log.e(TAG, logText);
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/SurfaceTraceReceiver.java b/tests/framework/base/activitymanager/util/src/android/server/am/SurfaceTraceReceiver.java
new file mode 100644
index 0000000..4b3953c
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/SurfaceTraceReceiver.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.fail;
+
+import android.graphics.RectF;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+// Parses a trace of surface commands from the WM (in real time)
+// and dispenses them via the SurfaceObserver interface.
+//
+// Data enters through addOutput
+public class SurfaceTraceReceiver {
+
+ final SurfaceObserver mObserver;
+
+ private State mState = State.CMD;
+ private String mCurrentWindowName = null;
+ private int mArgPosition = 0;
+ private float[] mTmpFloats = new float[10];
+ private int[] mTmpInts = new int[10];
+ private RectF mTmpRect = new RectF();
+ private byte[] mUnprocessedBytes = new byte[16384];
+ private byte[] mFullData = new byte[32768];
+ private int mUnprocessedBytesLength;
+
+ public interface SurfaceObserver {
+ default void setAlpha(String windowName, float alpha) {}
+ default void setLayer(String windowName, int layer) {}
+ default void setPosition(String windowName, float x, float y) {}
+ default void setSize(String widnowName, int width, int height) {}
+ default void setLayerStack(String windowName, int layerStack) {}
+ default void setMatrix(String windowName, float dsdx, float dtdx, float dsdy, float dtdy) {}
+ default void setCrop(String windowName, RectF crop) {}
+ default void setFinalCrop(String windowName, RectF finalCrop) {}
+ default void hide(String windowName) {}
+ default void show(String windowName) {}
+ default void setGeometryAppliesWithResize(String windowName) {}
+ default void openTransaction() {}
+ default void closeTransaction() {}
+ };
+
+ enum State {
+ CMD,
+ SET_ALPHA,
+ SET_LAYER,
+ SET_POSITION,
+ SET_SIZE,
+ SET_CROP,
+ SET_FINAL_CROP,
+ SET_LAYER_STACK,
+ SET_MATRIX,
+ HIDE,
+ SHOW,
+ GEOMETRY_APPLIES_WITH_RESIZE
+ };
+
+ SurfaceTraceReceiver(SurfaceObserver observer) {
+ mObserver = observer;
+ }
+
+ // Reset state and prepare to accept a new command.
+ void nextCmd(DataInputStream d) {
+ mState = State.CMD;
+ mCurrentWindowName = null;
+ mArgPosition = 0;
+
+ try {
+ // Consume the sigil
+ d.readByte();
+ d.readByte();
+ d.readByte();
+ d.readByte();
+ } catch (Exception e) {
+ logE("Exception consuming sigil: " + e);
+ }
+ }
+
+ // When the command parsing functions below are called, the window name
+ // will already be parsed. The responsibility of these functions
+ // is to parse other arguments 1 by 1 and accumlate them until the appropriate number
+ // is reached. At that point the parser should emit an event to the observer and
+ // call nextCmd
+ void parseAlpha(DataInputStream d) throws IOException {
+ float alpha = d.readFloat();
+ mObserver.setAlpha(mCurrentWindowName, alpha);
+ nextCmd(d);
+ }
+
+ void parseLayer(DataInputStream d) throws IOException {
+ int layer = d.readInt();
+ mObserver.setLayer(mCurrentWindowName, layer);
+ nextCmd(d);
+ }
+
+ void parsePosition(DataInputStream d) throws IOException {
+ mTmpFloats[mArgPosition] = d.readFloat();
+ mArgPosition++;
+ if (mArgPosition == 2) {
+ mObserver.setPosition(mCurrentWindowName, mTmpFloats[0], mTmpFloats[1]);
+ nextCmd(d);
+ }
+ }
+
+ void parseSize(DataInputStream d) throws IOException {
+ mTmpInts[mArgPosition] = d.readInt();
+ mArgPosition++;
+ if (mArgPosition == 2) {
+ mObserver.setSize(mCurrentWindowName, mTmpInts[0], mTmpInts[1]);
+ nextCmd(d);
+ }
+ }
+
+ // Careful Android rectangle rep is top-left-right-bottom awt is top-left-width-height
+ void parseCrop(DataInputStream d) throws IOException {
+ mTmpFloats[mArgPosition] = d.readFloat();
+ mArgPosition++;
+ if (mArgPosition == 4) {
+ mTmpRect.set(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2],
+ mTmpFloats[3]);
+ mObserver.setCrop(mCurrentWindowName, mTmpRect);
+ nextCmd(d);
+ }
+ }
+
+ void parseFinalCrop(DataInputStream d) throws IOException {
+ mTmpFloats[mArgPosition] = d.readInt();
+ mArgPosition++;
+ if (mArgPosition == 4) {
+ mTmpRect.set(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2],
+ mTmpFloats[3]);
+ mObserver.setFinalCrop(mCurrentWindowName, mTmpRect);
+ nextCmd(d);
+ }
+ }
+
+ void parseLayerStack(DataInputStream d) throws IOException {
+ int layerStack = d.readInt();
+ mObserver.setLayerStack(mCurrentWindowName, layerStack);
+ nextCmd(d);
+ }
+
+ void parseSetMatrix(DataInputStream d) throws IOException {
+ mTmpFloats[mArgPosition] = d.readFloat();
+ mArgPosition++;
+ if (mArgPosition == 4) {
+ mObserver.setMatrix(mCurrentWindowName, mTmpFloats[0],
+ mTmpFloats[1], mTmpFloats[2], mTmpFloats[3]);
+ nextCmd(d);
+ }
+ }
+
+ void parseHide(DataInputStream d) throws IOException {
+ mObserver.hide(mCurrentWindowName);
+ nextCmd(d);
+ }
+
+ void parseShow(DataInputStream d) throws IOException {
+ mObserver.show(mCurrentWindowName);
+ nextCmd(d);
+ }
+
+ void parseGeometryAppliesWithResize(DataInputStream d) throws IOException {
+ mObserver.setGeometryAppliesWithResize(mCurrentWindowName);
+ nextCmd(d);
+ }
+
+ public int indexAfterLastSigil(byte[] data, int offset, int length) {
+ int idx = offset + length - 1;
+ int sigilsNeeded = 4;
+ byte sigil = (byte) 0xfc;
+ while (idx > offset) {
+ if (data[idx] == sigil) {
+ sigilsNeeded--;
+ if (sigilsNeeded == 0) {
+ return idx + 4;
+ }
+ } else {
+ sigilsNeeded = 4;
+ }
+ idx--;
+ }
+ return idx; // idx == offset at this point
+ }
+
+ // The tricky bit here is ADB may break up our words, and not send us complete messages,
+ // or even complete integers! To ensure we process the data in appropriate chunks,
+ // We look for a sigil (0xfcfcfcfc) and only process data when it ends in as sigil.
+ // Otherwise we save it and wait to receive a sigil, then process the merged data.
+ public void addOutput(byte[] data, int offset, int length) {
+ byte[] combinedData = data;
+
+ // First we have to merge any unprocessed bytes from the last call in to
+ // a combined array.
+ if (mUnprocessedBytesLength > 0) {
+ System.arraycopy(mUnprocessedBytes, 0, mFullData, 0, mUnprocessedBytesLength);
+ System.arraycopy(data, offset, mFullData, mUnprocessedBytesLength, length);
+ combinedData = mFullData;
+ length = mUnprocessedBytesLength + length;
+ offset = 0;
+ mUnprocessedBytesLength = 0;
+ }
+
+ // Now we find the last sigil in our combined array. Everything before this index is
+ // a properly terminated message ready to be parsed.
+ int completedIndex = indexAfterLastSigil(combinedData, offset, length);
+ // If there are any bytes left after the last sigil, save them for next time.
+ if (completedIndex != length + offset) {
+ mUnprocessedBytesLength = (length + offset) - (completedIndex);
+ System.arraycopy(combinedData, completedIndex,
+ mUnprocessedBytes, 0, mUnprocessedBytesLength);
+ }
+ // If there was no sigil, we have nothing to process yet.
+ if (completedIndex <= offset) {
+ return;
+ }
+ ByteArrayInputStream b = new ByteArrayInputStream(combinedData, offset,
+ completedIndex - offset);
+ DataInputStream d = new DataInputStream(b);
+
+ // We may not receive an entire message at once (for example we may receive
+ // a command without its arguments), so we track our current state, over multiple
+ // addOutput calls. When we are in State.CMD it means we next expect a new command.
+ // If we are not expecting a command, then all commands with arguments, begin with
+ // a window name. Once we have the window name, individual parseAlpha,
+ // parseLayer, etc...statements will parse command arguments one at a time. Once
+ // the appropriate number of arguments is collected the observer will be invoked
+ // and the state reset. For commands which have no arguments (e.g. open/close transaction),
+ // parseCmd can emit the observer event and call nextCmd() right away.
+ try {
+ while (b.available() > 0) {
+ if (mState != State.CMD && mCurrentWindowName == null) {
+ mCurrentWindowName = d.readUTF();
+ if (b.available() == 0) {
+ return;
+ }
+ }
+ switch (mState) {
+ case CMD: {
+ String cmd = d.readUTF();
+ parseCmd(d, cmd);
+ break;
+ }
+ case SET_ALPHA: {
+ parseAlpha(d);
+ break;
+ }
+ case SET_LAYER: {
+ parseLayer(d);
+ break;
+ }
+ case SET_POSITION: {
+ parsePosition(d);
+ break;
+ }
+ case SET_SIZE: {
+ parseSize(d);
+ break;
+ }
+ case SET_CROP: {
+ parseCrop(d);
+ break;
+ }
+ case SET_FINAL_CROP: {
+ parseFinalCrop(d);
+ break;
+ }
+ case SET_LAYER_STACK: {
+ parseLayerStack(d);
+ break;
+ }
+ case SET_MATRIX: {
+ parseSetMatrix(d);
+ break;
+ }
+ case HIDE: {
+ parseHide(d);
+ break;
+ }
+ case SHOW: {
+ parseShow(d);
+ break;
+ }
+ case GEOMETRY_APPLIES_WITH_RESIZE: {
+ parseGeometryAppliesWithResize(d);
+ break;
+ }
+ }
+ }
+ } catch (Exception e) {
+ logE("Error in surface trace receiver: " + e.toString());
+ }
+ }
+
+ void parseCmd(DataInputStream d, String cmd) {
+ switch (cmd) {
+ case "Alpha":
+ mState = State.SET_ALPHA;
+ break;
+ case "Layer":
+ mState = State.SET_LAYER;
+ break;
+ case "Position":
+ mState = State.SET_POSITION;
+ break;
+ case "Size":
+ mState = State.SET_SIZE;
+ break;
+ case "Crop":
+ mState = State.SET_CROP;
+ break;
+ case "FinalCrop":
+ mState = State.SET_FINAL_CROP;
+ break;
+ case "LayerStack":
+ mState = State.SET_LAYER_STACK;
+ break;
+ case "Matrix":
+ mState = State.SET_MATRIX;
+ break;
+ case "Hide":
+ mState = State.HIDE;
+ break;
+ case "Show":
+ mState = State.SHOW;
+ break;
+ case "GeometryAppliesWithResize":
+ mState = State.GEOMETRY_APPLIES_WITH_RESIZE;
+ break;
+ case "OpenTransaction":
+ mObserver.openTransaction();
+ nextCmd(d);
+ break;
+ case "CloseTransaction":
+ mObserver.closeTransaction();
+ nextCmd(d);
+ break;
+ default:
+ fail("Unexpected surface command: " + cmd);
+ break;
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
new file mode 100644
index 0000000..5d5366a0
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.content.ComponentName;
+import android.support.annotation.Nullable;
+
+public class WaitForValidActivityState {
+ @Nullable
+ public final String componentName;
+ @Nullable
+ public final String windowName;
+ /** Use {@link #componentName} and {@link #windowName}. */
+ @Deprecated
+ @Nullable
+ public final String activityName;
+ public final int stackId;
+ public final int windowingMode;
+ public final int activityType;
+
+ public static WaitForValidActivityState forWindow(final String windowName) {
+ return new Builder().setWindowName(windowName).build();
+ }
+
+ public WaitForValidActivityState(final ComponentName activityName) {
+ this.componentName = activityName.flattenToShortString();
+ this.windowName = activityName.flattenToString();
+ this.activityName = getSimpleClassName(activityName);
+ this.stackId = INVALID_STACK_ID;
+ this.windowingMode = WINDOWING_MODE_UNDEFINED;
+ this.activityType = ACTIVITY_TYPE_UNDEFINED;
+ }
+
+ /** Use {@link #WaitForValidActivityState(ComponentName)}. */
+ @Deprecated
+ public WaitForValidActivityState(String activityName) {
+ this.componentName = null;
+ this.windowName = null;
+ this.activityName = activityName;
+ this.stackId = INVALID_STACK_ID;
+ this.windowingMode = WINDOWING_MODE_UNDEFINED;
+ this.activityType = ACTIVITY_TYPE_UNDEFINED;
+ }
+
+ private WaitForValidActivityState(final Builder builder) {
+ this.componentName = builder.mComponentName;
+ this.windowName = builder.mWindowName;
+ this.activityName = builder.mActivityName;
+ this.stackId = builder.mStackId;
+ this.windowingMode = builder.mWindowingMode;
+ this.activityType = builder.mActivityType;
+ }
+
+ /**
+ * @return the class name of <code>componentName</code>, either fully qualified class name or in
+ * a shortened form (WITHOUT a leading '.') if it is a suffix of the package.
+ * @see ComponentName#getShortClassName()
+ */
+ private static String getSimpleClassName(final ComponentName componentName) {
+ final String packageName = componentName.getPackageName();
+ final String className = componentName.getClassName();
+ if (className.startsWith(packageName)) {
+ final int packageNameLen = packageName.length();
+ if (className.length() > packageNameLen && className.charAt(packageNameLen) == '.') {
+ return className.substring(packageNameLen + 1);
+ }
+ }
+ return className;
+ }
+
+ public static class Builder {
+ @Nullable
+ private String mComponentName = null;
+ @Nullable
+ private String mWindowName = null;
+ @Nullable
+ private String mActivityName = null;
+ private int mStackId = INVALID_STACK_ID;
+ private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
+
+ private Builder() {}
+
+ public Builder(final ComponentName activityName) {
+ mComponentName = activityName.flattenToShortString();
+ mWindowName = activityName.flattenToString();
+ mActivityName = getSimpleClassName(activityName);
+ }
+
+ /** Use {@link #Builder(ComponentName)}. */
+ @Deprecated
+ public Builder(String activityName) {
+ mActivityName = activityName;
+ }
+
+ private Builder setWindowName(String windowName) {
+ mWindowName = windowName;
+ return this;
+ }
+
+ public Builder setStackId(int stackId) {
+ mStackId = stackId;
+ return this;
+ }
+
+ public Builder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ public Builder setActivityType(int activityType) {
+ mActivityType = activityType;
+ return this;
+ }
+
+ public WaitForValidActivityState build() {
+ return new WaitForValidActivityState(this);
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
new file mode 100644
index 0000000..1eb855e
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.am.ProtoExtractors.extract;
+import static android.server.am.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.fail;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.view.nano.DisplayInfoProto;
+
+import com.android.server.wm.proto.nano.AppTransitionProto;
+import com.android.server.wm.proto.nano.AppWindowTokenProto;
+import com.android.server.wm.proto.nano.ConfigurationContainerProto;
+import com.android.server.wm.proto.nano.DisplayFramesProto;
+import com.android.server.wm.proto.nano.DisplayProto;
+import com.android.server.wm.proto.nano.IdentifierProto;
+import com.android.server.wm.proto.nano.PinnedStackControllerProto;
+import com.android.server.wm.proto.nano.StackProto;
+import com.android.server.wm.proto.nano.TaskProto;
+import com.android.server.wm.proto.nano.WindowContainerProto;
+import com.android.server.wm.proto.nano.WindowManagerServiceProto;
+import com.android.server.wm.proto.nano.WindowStateAnimatorProto;
+import com.android.server.wm.proto.nano.WindowStateProto;
+import com.android.server.wm.proto.nano.WindowSurfaceControllerProto;
+import com.android.server.wm.proto.nano.WindowTokenProto;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class WindowManagerState {
+ public static final String TRANSIT_ACTIVITY_OPEN = "TRANSIT_ACTIVITY_OPEN";
+ public static final String TRANSIT_ACTIVITY_CLOSE = "TRANSIT_ACTIVITY_CLOSE";
+ public static final String TRANSIT_TASK_OPEN = "TRANSIT_TASK_OPEN";
+ public static final String TRANSIT_TASK_CLOSE = "TRANSIT_TASK_CLOSE";
+
+ public static final String TRANSIT_WALLPAPER_OPEN = "TRANSIT_WALLPAPER_OPEN";
+ public static final String TRANSIT_WALLPAPER_CLOSE = "TRANSIT_WALLPAPER_CLOSE";
+ public static final String TRANSIT_WALLPAPER_INTRA_OPEN = "TRANSIT_WALLPAPER_INTRA_OPEN";
+ public static final String TRANSIT_WALLPAPER_INTRA_CLOSE = "TRANSIT_WALLPAPER_INTRA_CLOSE";
+
+ public static final String TRANSIT_KEYGUARD_GOING_AWAY = "TRANSIT_KEYGUARD_GOING_AWAY";
+ public static final String TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER =
+ "TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
+ public static final String TRANSIT_KEYGUARD_OCCLUDE = "TRANSIT_KEYGUARD_OCCLUDE";
+ public static final String TRANSIT_KEYGUARD_UNOCCLUDE = "TRANSIT_KEYGUARD_UNOCCLUDE";
+
+ public static final String APP_STATE_IDLE = "APP_STATE_IDLE";
+
+ private static final String DUMPSYS_WINDOW = "dumpsys window -a --proto";
+
+ private static final String STARTING_WINDOW_PREFIX = "Starting ";
+ private static final String DEBUGGER_WINDOW_PREFIX = "Waiting For Debugger: ";
+
+ // Windows in z-order with the top most at the front of the list.
+ private List<WindowState> mWindowStates = new ArrayList();
+ // Stacks in z-order with the top most at the front of the list, starting with primary display.
+ private final List<WindowStack> mStacks = new ArrayList();
+ // Stacks on all attached displays, in z-order with the top most at the front of the list.
+ private final Map<Integer, List<WindowStack>> mDisplayStacks
+ = new HashMap<>();
+ private List<Display> mDisplays = new ArrayList();
+ private String mFocusedWindow = null;
+ private String mFocusedApp = null;
+ private String mLastTransition = null;
+ private String mAppTransitionState = null;
+ private String mInputMethodWindowAppToken = null;
+ private Rect mDefaultPinnedStackBounds = new Rect();
+ private Rect mPinnedStackMovementBounds = new Rect();
+ private final LinkedList<String> mSysDump = new LinkedList();
+ private int mRotation;
+ private int mLastOrientation;
+ private boolean mDisplayFrozen;
+ private boolean mIsDockedStackMinimized;
+
+ public void computeState() {
+ // It is possible the system is in the middle of transition to the right state when we get
+ // the dump. We try a few times to get the information we need before giving up.
+ int retriesLeft = 3;
+ boolean retry = false;
+ byte[] dump = null;
+
+ log("==============================");
+ log(" WindowManagerState ");
+ log("==============================");
+ do {
+ if (retry) {
+ log("***Incomplete WM state. Retrying...");
+ // Wait half a second between retries for window manager to finish transitioning...
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ log(e.toString());
+ // Well I guess we are not waiting...
+ }
+ }
+
+ dump = executeShellCommand(DUMPSYS_WINDOW);
+ try {
+ parseSysDumpProto(dump);
+ } catch (InvalidProtocolBufferNanoException ex) {
+ throw new RuntimeException("Failed to parse dumpsys:\n"
+ + new String(dump, StandardCharsets.UTF_8), ex);
+ }
+
+ retry = mWindowStates.isEmpty() || mFocusedApp == null;
+ } while (retry && retriesLeft-- > 0);
+
+ if (mWindowStates.isEmpty()) {
+ logE("No Windows found...");
+ }
+ if (mFocusedWindow == null) {
+ logE("No Focused Window...");
+ }
+ if (mFocusedApp == null) {
+ logE("No Focused App...");
+ }
+ }
+
+ private byte[] executeShellCommand(String cmd) {
+ try {
+ ParcelFileDescriptor pfd =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand(cmd);
+ byte[] buf = new byte[512];
+ int bytesRead;
+ FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+ while ((bytesRead = fis.read(buf)) != -1) {
+ stdout.write(buf, 0, bytesRead);
+ }
+ fis.close();
+ return stdout.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
+ reset();
+ WindowManagerServiceProto state = WindowManagerServiceProto.parseFrom(sysDump);
+ List<WindowState> allWindows = new ArrayList<>();
+ Map<String, WindowState> windowMap = new HashMap<>();
+ if (state.focusedWindow != null) {
+ mFocusedWindow = state.focusedWindow.title;
+ }
+ mFocusedApp = state.focusedApp;
+ for (int i = 0; i < state.rootWindowContainer.displays.length; i++) {
+ DisplayProto displayProto = state.rootWindowContainer.displays[i];
+ final Display display = new Display(displayProto);
+ mDisplays.add(display);
+ allWindows.addAll(display.getWindows());
+ List<WindowStack> stacks = new ArrayList<>();
+ for (int j = 0; j < displayProto.stacks.length; j++) {
+ StackProto stackProto = displayProto.stacks[j];
+ final WindowStack stack = new WindowStack(stackProto);
+ mStacks.add(stack);
+ stacks.add(stack);
+ allWindows.addAll(stack.getWindows());
+ }
+ mDisplayStacks.put(display.mDisplayId, stacks);
+
+ // use properties from the default display only
+ if (display.getDisplayId() == DEFAULT_DISPLAY) {
+ if (displayProto.dockedStackDividerController != null) {
+ mIsDockedStackMinimized =
+ displayProto.dockedStackDividerController.minimizedDock;
+ }
+ PinnedStackControllerProto pinnedStackProto = displayProto.pinnedStackController;
+ if (pinnedStackProto != null) {
+ mDefaultPinnedStackBounds = extract(pinnedStackProto.defaultBounds);
+ mPinnedStackMovementBounds = extract(pinnedStackProto.movementBounds);
+ }
+ }
+ }
+ for (WindowState w : allWindows) {
+ windowMap.put(w.getToken(), w);
+ }
+ for (int i = 0; i < state.rootWindowContainer.windows.length; i++) {
+ IdentifierProto identifierProto = state.rootWindowContainer.windows[i];
+ String hash_code = Integer.toHexString(identifierProto.hashCode);
+ mWindowStates.add(windowMap.get(hash_code));
+ }
+ if (state.inputMethodWindow != null) {
+ mInputMethodWindowAppToken = Integer.toHexString(state.inputMethodWindow.hashCode);
+ }
+ mDisplayFrozen = state.displayFrozen;
+ mRotation = state.rotation;
+ mLastOrientation = state.lastOrientation;
+ AppTransitionProto appTransitionProto = state.appTransition;
+ int appState = 0;
+ int lastTransition = 0;
+ if (appTransitionProto != null) {
+ appState = appTransitionProto.appTransitionState;
+ lastTransition = appTransitionProto.lastUsedAppTransition;
+ }
+ mAppTransitionState = appStateToString(appState);
+ mLastTransition = appTransitionToString(lastTransition);
+ }
+
+ static String appStateToString(int appState) {
+ switch (appState) {
+ case AppTransitionProto.APP_STATE_IDLE:
+ return "APP_STATE_IDLE";
+ case AppTransitionProto.APP_STATE_READY:
+ return "APP_STATE_READY";
+ case AppTransitionProto.APP_STATE_RUNNING:
+ return "APP_STATE_RUNNING";
+ case AppTransitionProto.APP_STATE_TIMEOUT:
+ return "APP_STATE_TIMEOUT";
+ default:
+ fail("Invalid AppTransitionState");
+ return null;
+ }
+ }
+
+ static String appTransitionToString(int transition) {
+ switch (transition) {
+ case AppTransitionProto.TRANSIT_UNSET: {
+ return "TRANSIT_UNSET";
+ }
+ case AppTransitionProto.TRANSIT_NONE: {
+ return "TRANSIT_NONE";
+ }
+ case AppTransitionProto.TRANSIT_ACTIVITY_OPEN: {
+ return TRANSIT_ACTIVITY_OPEN;
+ }
+ case AppTransitionProto.TRANSIT_ACTIVITY_CLOSE: {
+ return TRANSIT_ACTIVITY_CLOSE;
+ }
+ case AppTransitionProto.TRANSIT_TASK_OPEN: {
+ return TRANSIT_TASK_OPEN;
+ }
+ case AppTransitionProto.TRANSIT_TASK_CLOSE: {
+ return TRANSIT_TASK_CLOSE;
+ }
+ case AppTransitionProto.TRANSIT_TASK_TO_FRONT: {
+ return "TRANSIT_TASK_TO_FRONT";
+ }
+ case AppTransitionProto.TRANSIT_TASK_TO_BACK: {
+ return "TRANSIT_TASK_TO_BACK";
+ }
+ case AppTransitionProto.TRANSIT_WALLPAPER_CLOSE: {
+ return TRANSIT_WALLPAPER_CLOSE;
+ }
+ case AppTransitionProto.TRANSIT_WALLPAPER_OPEN: {
+ return TRANSIT_WALLPAPER_OPEN;
+ }
+ case AppTransitionProto.TRANSIT_WALLPAPER_INTRA_OPEN: {
+ return TRANSIT_WALLPAPER_INTRA_OPEN;
+ }
+ case AppTransitionProto.TRANSIT_WALLPAPER_INTRA_CLOSE: {
+ return TRANSIT_WALLPAPER_INTRA_CLOSE;
+ }
+ case AppTransitionProto.TRANSIT_TASK_OPEN_BEHIND: {
+ return "TRANSIT_TASK_OPEN_BEHIND";
+ }
+ case AppTransitionProto.TRANSIT_ACTIVITY_RELAUNCH: {
+ return "TRANSIT_ACTIVITY_RELAUNCH";
+ }
+ case AppTransitionProto.TRANSIT_DOCK_TASK_FROM_RECENTS: {
+ return "TRANSIT_DOCK_TASK_FROM_RECENTS";
+ }
+ case AppTransitionProto.TRANSIT_KEYGUARD_GOING_AWAY: {
+ return TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+ case AppTransitionProto.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
+ return TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+ }
+ case AppTransitionProto.TRANSIT_KEYGUARD_OCCLUDE: {
+ return TRANSIT_KEYGUARD_OCCLUDE;
+ }
+ case AppTransitionProto.TRANSIT_KEYGUARD_UNOCCLUDE: {
+ return TRANSIT_KEYGUARD_UNOCCLUDE;
+ }
+ default: {
+ fail("Invalid lastUsedAppTransition");
+ return null;
+ }
+ }
+ }
+
+ void getMatchingWindowTokens(final String windowName, List<String> tokenList) {
+ tokenList.clear();
+
+ for (WindowState ws : mWindowStates) {
+ if (windowName.equals(ws.getName())) {
+ tokenList.add(ws.getToken());
+ }
+ }
+ }
+
+ public void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
+ windowList.clear();
+ for (WindowState ws : mWindowStates) {
+ if (ws.isShown() && windowName.equals(ws.getName())) {
+ windowList.add(ws);
+ }
+ }
+ }
+
+ public boolean containsExitingWindow() {
+ for (WindowState ws : mWindowStates) {
+ if (ws.isExitingWindow()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public WindowState getWindowByPackageName(String packageName, int windowType) {
+ for (WindowState ws : mWindowStates) {
+ final String name = ws.getName();
+ if (name == null || !name.contains(packageName)) {
+ continue;
+ }
+ if (windowType != ws.getType()) {
+ continue;
+ }
+ return ws;
+ }
+
+ return null;
+ }
+
+ public void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
+ List<WindowState> outWindowList) {
+ outWindowList.clear();
+ for (WindowState ws : mWindowStates) {
+ final String name = ws.getName();
+ if (name == null || !name.contains(packageName)) {
+ continue;
+ }
+ if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
+ continue;
+ }
+ outWindowList.add(ws);
+ }
+ }
+
+ WindowState getWindowStateForAppToken(String appToken) {
+ for (WindowState ws : mWindowStates) {
+ if (ws.getToken().equals(appToken)) {
+ return ws;
+ }
+ }
+ return null;
+ }
+
+ Display getDisplay(int displayId) {
+ for (Display display : mDisplays) {
+ if (displayId == display.getDisplayId()) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ String getFrontWindow() {
+ if (mWindowStates == null || mWindowStates.isEmpty()) {
+ return null;
+ }
+ return mWindowStates.get(0).getName();
+ }
+
+ public String getFocusedWindow() {
+ return mFocusedWindow;
+ }
+
+ public String getFocusedApp() {
+ return mFocusedApp;
+ }
+
+ String getLastTransition() {
+ return mLastTransition;
+ }
+
+ String getAppTransitionState() {
+ return mAppTransitionState;
+ }
+
+ int getFrontStackId(int displayId) {
+ return mDisplayStacks.get(displayId).get(0).mStackId;
+ }
+
+ int getFrontStackActivityType(int displayId) {
+ return mDisplayStacks.get(displayId).get(0).getActivityType();
+ }
+
+ public int getRotation() {
+ return mRotation;
+ }
+
+ int getLastOrientation() {
+ return mLastOrientation;
+ }
+
+ boolean containsStack(int stackId) {
+ for (WindowStack stack : mStacks) {
+ if (stackId == stack.mStackId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean containsStack(int windowingMode, int activityType) {
+ for (WindowStack stack : mStacks) {
+ if (activityType != ACTIVITY_TYPE_UNDEFINED
+ && activityType != stack.getActivityType()) {
+ continue;
+ }
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && windowingMode != stack.getWindowingMode()) {
+ continue;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if there exists a window record with matching windowName.
+ */
+ boolean containsWindow(String windowName) {
+ for (WindowState window : mWindowStates) {
+ if (window.getName().equals(windowName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if at least one window which matches provided window name is visible.
+ */
+ boolean isWindowVisible(String windowName) {
+ for (WindowState window : mWindowStates) {
+ if (window.getName().equals(windowName)) {
+ if (window.isShown()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean allWindowsVisible(String windowName) {
+ boolean allVisible = false;
+ for (WindowState window : mWindowStates) {
+ if (window.getName().equals(windowName)) {
+ if (!window.isShown()) {
+ log("[VISIBLE] not visible" + windowName);
+ return false;
+ }
+ log("[VISIBLE] visible" + windowName);
+ allVisible = true;
+ }
+ }
+ return allVisible;
+ }
+
+ WindowStack getStack(int stackId) {
+ for (WindowStack stack : mStacks) {
+ if (stackId == stack.mStackId) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ WindowStack getStandardStackByWindowingMode(int windowingMode) {
+ for (WindowStack stack : mStacks) {
+ if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+ continue;
+ }
+ if (stack.getWindowingMode() == windowingMode) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ /** Get the stack position on its display. */
+ int getStackIndexByActivityType(int activityType) {
+ for (Integer displayId : mDisplayStacks.keySet()) {
+ List<WindowStack> stacks = mDisplayStacks.get(displayId);
+ for (int i = 0; i < stacks.size(); i++) {
+ if (activityType == stacks.get(i).getActivityType()) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ WindowState getInputMethodWindowState() {
+ return getWindowStateForAppToken(mInputMethodWindowAppToken);
+ }
+
+ Rect getStableBounds() {
+ return getDisplay(DEFAULT_DISPLAY).mStableBounds;
+ }
+
+ Rect getDefaultPinnedStackBounds() {
+ return new Rect(mDefaultPinnedStackBounds);
+ }
+
+ Rect getPinnedStackMomentBounds() {
+ return new Rect(mPinnedStackMovementBounds);
+ }
+
+ WindowState findFirstWindowWithType(int type) {
+ for (WindowState window : mWindowStates) {
+ if (window.getType() == type) {
+ return window;
+ }
+ }
+ return null;
+ }
+
+ public boolean isDisplayFrozen() {
+ return mDisplayFrozen;
+ }
+
+ public boolean isDockedStackMinimized() {
+ return mIsDockedStackMinimized;
+ }
+
+ public int getZOrder(WindowState w) {
+ return mWindowStates.size() - mWindowStates.indexOf(w);
+ }
+
+ private void reset() {
+ mSysDump.clear();
+ mStacks.clear();
+ mDisplays.clear();
+ mWindowStates.clear();
+ mDisplayStacks.clear();
+ mFocusedWindow = null;
+ mFocusedApp = null;
+ mLastTransition = null;
+ mInputMethodWindowAppToken = null;
+ mIsDockedStackMinimized = false;
+ mDefaultPinnedStackBounds.setEmpty();
+ mPinnedStackMovementBounds.setEmpty();
+ mRotation = 0;
+ mLastOrientation = 0;
+ mDisplayFrozen = false;
+ }
+
+ static class WindowStack extends WindowContainer {
+
+ int mStackId;
+ ArrayList<WindowTask> mTasks = new ArrayList<>();
+ boolean mWindowAnimationBackgroundSurfaceShowing;
+
+ WindowStack(StackProto proto) {
+ super(proto.windowContainer);
+ mStackId = proto.id;
+ mFullscreen = proto.fillsParent;
+ mBounds = extract(proto.bounds);
+ for (int i = 0; i < proto.tasks.length; i++) {
+ TaskProto taskProto = proto.tasks[i];
+ WindowTask task = new WindowTask(taskProto);
+ mTasks.add(task);
+ mSubWindows.addAll(task.getWindows());
+ }
+ mWindowAnimationBackgroundSurfaceShowing = proto.animationBackgroundSurfaceIsDimming;
+ }
+
+ WindowTask getTask(int taskId) {
+ for (WindowTask task : mTasks) {
+ if (taskId == task.mTaskId) {
+ return task;
+ }
+ }
+ return null;
+ }
+
+ boolean isWindowAnimationBackgroundSurfaceShowing() {
+ return mWindowAnimationBackgroundSurfaceShowing;
+ }
+ }
+
+ static class WindowTask extends WindowContainer {
+
+ int mTaskId;
+ Rect mTempInsetBounds;
+ List<String> mAppTokens = new ArrayList<>();
+
+ WindowTask(TaskProto proto) {
+ super(proto.windowContainer);
+ mTaskId = proto.id;
+ mFullscreen = proto.fillsParent;
+ mBounds = extract(proto.bounds);
+ for (int i = 0; i < proto.appWindowTokens.length; i++) {
+ AppWindowTokenProto appWindowTokenProto = proto.appWindowTokens[i];
+ mAppTokens.add(appWindowTokenProto.name);
+ WindowTokenProto windowTokenProto = appWindowTokenProto.windowToken;
+ for (int j = 0; j < windowTokenProto.windows.length; j++) {
+ WindowStateProto windowProto = windowTokenProto.windows[j];
+ WindowState window = new WindowState(windowProto);
+ mSubWindows.add(window);
+ mSubWindows.addAll(window.getWindows());
+ }
+ }
+ mTempInsetBounds = extract(proto.tempInsetBounds);
+ }
+ }
+
+ static class ConfigurationContainer {
+ final Configuration mOverrideConfiguration = new Configuration();
+ final Configuration mFullConfiguration = new Configuration();
+ final Configuration mMergedOverrideConfiguration = new Configuration();
+
+ ConfigurationContainer(ConfigurationContainerProto proto) {
+ if (proto == null) {
+ return;
+ }
+ mOverrideConfiguration.setTo(extract(proto.overrideConfiguration));
+ mFullConfiguration.setTo(extract(proto.fullConfiguration));
+ mMergedOverrideConfiguration.setTo(extract(proto.mergedOverrideConfiguration));
+ }
+
+ int getWindowingMode() {
+ if (mFullConfiguration == null) {
+ return WINDOWING_MODE_UNDEFINED;
+ }
+ return mFullConfiguration.windowConfiguration.getWindowingMode();
+ }
+
+ int getActivityType() {
+ if (mFullConfiguration == null) {
+ return ACTIVITY_TYPE_UNDEFINED;
+ }
+ return mFullConfiguration.windowConfiguration.getActivityType();
+ }
+ }
+
+ static abstract class WindowContainer extends ConfigurationContainer {
+
+ protected boolean mFullscreen;
+ protected Rect mBounds;
+ protected int mOrientation;
+ protected List<WindowState> mSubWindows = new ArrayList<>();
+
+ WindowContainer(WindowContainerProto proto) {
+ super(proto.configurationContainer);
+ mOrientation = proto.orientation;
+ }
+
+ Rect getBounds() {
+ return mBounds;
+ }
+
+ boolean isFullscreen() {
+ return mFullscreen;
+ }
+
+ List<WindowState> getWindows() {
+ return mSubWindows;
+ }
+ }
+
+ static class Display extends WindowContainer {
+
+ private final int mDisplayId;
+ private Rect mDisplayRect = new Rect();
+ private Rect mAppRect = new Rect();
+ private int mDpi;
+ private Rect mStableBounds;
+
+ public Display(DisplayProto proto) {
+ super(proto.windowContainer);
+ mDisplayId = proto.id;
+ for (int i = 0; i < proto.aboveAppWindows.length; i++) {
+ addWindowsFromTokenProto(proto.aboveAppWindows[i]);
+ }
+ for (int i = 0; i < proto.belowAppWindows.length; i++) {
+ addWindowsFromTokenProto(proto.belowAppWindows[i]);
+ }
+ for (int i = 0; i < proto.imeWindows.length; i++) {
+ addWindowsFromTokenProto(proto.imeWindows[i]);
+ }
+ mDpi = proto.dpi;
+ DisplayInfoProto infoProto = proto.displayInfo;
+ if (infoProto != null) {
+ mDisplayRect.set(0, 0, infoProto.logicalWidth, infoProto.logicalHeight);
+ mAppRect.set(0, 0, infoProto.appWidth, infoProto.appHeight);
+ }
+ final DisplayFramesProto displayFramesProto = proto.displayFrames;
+ if (displayFramesProto != null) {
+ mStableBounds = extract(displayFramesProto.stableBounds);
+ }
+ }
+
+ private void addWindowsFromTokenProto(WindowTokenProto proto) {
+ for (int j = 0; j < proto.windows.length; j++) {
+ WindowStateProto windowProto = proto.windows[j];
+ WindowState childWindow = new WindowState(windowProto);
+ mSubWindows.add(childWindow);
+ mSubWindows.addAll(childWindow.getWindows());
+ }
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
+ int getDpi() {
+ return mDpi;
+ }
+
+ Rect getDisplayRect() {
+ return mDisplayRect;
+ }
+
+ Rect getAppRect() {
+ return mAppRect;
+ }
+
+ @Override
+ public String toString() {
+ return "Display #" + mDisplayId + ": mDisplayRect=" + mDisplayRect
+ + " mAppRect=" + mAppRect;
+ }
+ }
+
+ public static class WindowState extends WindowContainer {
+
+ private static final int WINDOW_TYPE_NORMAL = 0;
+ private static final int WINDOW_TYPE_STARTING = 1;
+ private static final int WINDOW_TYPE_EXITING = 2;
+ private static final int WINDOW_TYPE_DEBUGGER = 3;
+
+ private String mName;
+ private final String mAppToken;
+ private final int mWindowType;
+ private int mType = 0;
+ private int mDisplayId;
+ private int mStackId;
+ private int mLayer;
+ private boolean mShown;
+ private Rect mContainingFrame = new Rect();
+ private Rect mParentFrame = new Rect();
+ private Rect mContentFrame = new Rect();
+ private Rect mFrame = new Rect();
+ private Rect mSurfaceInsets = new Rect();
+ private Rect mContentInsets = new Rect();
+ private Rect mGivenContentInsets = new Rect();
+ private Rect mCrop = new Rect();
+
+ WindowState(WindowStateProto proto) {
+ super(proto.windowContainer);
+ IdentifierProto identifierProto = proto.identifier;
+ mName = identifierProto.title;
+ mAppToken = Integer.toHexString(identifierProto.hashCode);
+ mDisplayId = proto.displayId;
+ mStackId = proto.stackId;
+ if (proto.attributes != null) {
+ mType = proto.attributes.type;
+ }
+ WindowStateAnimatorProto animatorProto = proto.animator;
+ if (animatorProto != null) {
+ if (animatorProto.surface != null) {
+ WindowSurfaceControllerProto surfaceProto = animatorProto.surface;
+ mShown = surfaceProto.shown;
+ mLayer = surfaceProto.layer;
+ }
+ mCrop = extract(animatorProto.lastClipRect);
+ }
+ mGivenContentInsets = extract(proto.givenContentInsets);
+ mFrame = extract(proto.frame);
+ mContainingFrame = extract(proto.containingFrame);
+ mParentFrame = extract(proto.parentFrame);
+ mContentFrame = extract(proto.contentFrame);
+ mContentInsets = extract(proto.contentInsets);
+ mSurfaceInsets = extract(proto.surfaceInsets);
+ if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
+ mWindowType = WINDOW_TYPE_STARTING;
+ // Existing code depends on the prefix being removed
+ mName = mName.substring(STARTING_WINDOW_PREFIX.length());
+ } else if (proto.animatingExit) {
+ mWindowType = WINDOW_TYPE_EXITING;
+ } else if (mName.startsWith(DEBUGGER_WINDOW_PREFIX)) {
+ mWindowType = WINDOW_TYPE_STARTING;
+ mName = mName.substring(DEBUGGER_WINDOW_PREFIX.length());
+ } else {
+ mWindowType = 0;
+ }
+ for (int i = 0; i < proto.childWindows.length; i++) {
+ WindowStateProto childProto = proto.childWindows[i];
+ WindowState childWindow = new WindowState(childProto);
+ mSubWindows.add(childWindow);
+ mSubWindows.addAll(childWindow.getWindows());
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ String getToken() {
+ return mAppToken;
+ }
+
+ boolean isStartingWindow() {
+ return mWindowType == WINDOW_TYPE_STARTING;
+ }
+
+ boolean isExitingWindow() {
+ return mWindowType == WINDOW_TYPE_EXITING;
+ }
+
+ boolean isDebuggerWindow() {
+ return mWindowType == WINDOW_TYPE_DEBUGGER;
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
+ int getStackId() {
+ return mStackId;
+ }
+
+ Rect getContainingFrame() {
+ return mContainingFrame;
+ }
+
+ public Rect getFrame() {
+ return mFrame;
+ }
+
+ Rect getSurfaceInsets() {
+ return mSurfaceInsets;
+ }
+
+ Rect getContentInsets() {
+ return mContentInsets;
+ }
+
+ Rect getGivenContentInsets() {
+ return mGivenContentInsets;
+ }
+
+ public Rect getContentFrame() {
+ return mContentFrame;
+ }
+
+ Rect getParentFrame() {
+ return mParentFrame;
+ }
+
+ Rect getCrop() {
+ return mCrop;
+ }
+
+ boolean isShown() {
+ return mShown;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ private String getWindowTypeSuffix(int windowType) {
+ switch (windowType) {
+ case WINDOW_TYPE_STARTING:
+ return " STARTING";
+ case WINDOW_TYPE_EXITING:
+ return " EXITING";
+ case WINDOW_TYPE_DEBUGGER:
+ return " DEBUGGER";
+ default:
+ break;
+ }
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return "WindowState: {" + mAppToken + " " + mName
+ + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
+ + " cf=" + mContainingFrame + " pf=" + mParentFrame;
+ }
+ }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java b/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java
new file mode 100644
index 0000000..9877209
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/settings/SettingsSession.java
@@ -0,0 +1,164 @@
+package android.server.am.settings;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings.SettingNotFoundException;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper class to save, set, and restore global system-level preferences.
+ * <p>
+ * To use this class, testing APK must be self-instrumented and have
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}.
+ * <p>
+ * A test that changes system-level preferences can be written easily and reliably.
+ * <pre>
+ * static class PrefSession extends SettingsSession<String> {
+ * PrefSession() {
+ * super(android.provider.Settings.Secure.getUriFor(
+ * android.provider.Settings.Secure.PREFERENCE_KEY),
+ * android.provider.Settings.Secure::getString,
+ * android.provider.Settings.Secure::putString);
+ * }
+ * }
+ *
+ * @Test
+ * public void doTest() throws Exception {
+ * try (final PrefSession prefSession = new PrefSession()) {
+ * prefSession.set("value 1");
+ * doTest1();
+ * prefSession.set("value 2");
+ * doTest2();
+ * }
+ * }
+ * </pre>
+ */
+public class SettingsSession<T> implements AutoCloseable {
+ private static final String TAG = SettingsSession.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ @FunctionalInterface
+ public interface SettingsGetter<T> {
+ T get(ContentResolver cr, String key) throws SettingNotFoundException;
+ }
+
+ @FunctionalInterface
+ public interface SettingsSetter<T> {
+ void set(ContentResolver cr, String key, T value);
+ }
+
+ /**
+ * To debug to detect nested sessions for the same key. Enabled when {@link #DEBUG} is true.
+ * Note that nested sessions can be merged into one session.
+ */
+ private static final SessionCounters sSessionCounters = new SessionCounters();
+
+ private final Uri mUri;
+ private final SettingsGetter<T> mGetter;
+ private final SettingsSetter<T> mSetter;
+ private final boolean mHasInitialValue;
+ private final T mInitialValue;
+
+ public SettingsSession(final Uri uri, final SettingsGetter<T> getter,
+ final SettingsSetter<T> setter) {
+ mUri = uri;
+ mGetter = getter;
+ mSetter = setter;
+ T initialValue;
+ boolean hasInitialValue;
+ try {
+ initialValue = get(uri, getter);
+ hasInitialValue = true;
+ } catch (SettingNotFoundException e) {
+ initialValue = null;
+ hasInitialValue = false;
+ }
+ mInitialValue = initialValue;
+ mHasInitialValue = hasInitialValue;
+ if (DEBUG) {
+ Log.i(TAG, "start: uri=" + uri
+ + (mHasInitialValue ? " value=" + mInitialValue : " undefined"));
+ sSessionCounters.open(uri);
+ }
+ }
+
+ public void set(final @NonNull T value) throws Exception {
+ put(mUri, mSetter, value);
+ if (DEBUG) {
+ Log.i(TAG, " set: uri=" + mUri + " value=" + value);
+ }
+ }
+
+ public T get() throws SettingNotFoundException {
+ return get(mUri, mGetter);
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (mHasInitialValue) {
+ put(mUri, mSetter, mInitialValue);
+ if (DEBUG) {
+ Log.i(TAG, "close: uri=" + mUri + " value=" + mInitialValue);
+ }
+ } else {
+ try {
+ delete(mUri);
+ if (DEBUG) {
+ Log.i(TAG, "close: uri=" + mUri + " deleted");
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Can't delete settings " + mUri, e);
+ }
+ }
+ if (DEBUG) {
+ sSessionCounters.close(mUri);
+ }
+ }
+
+ private static <T> void put(final Uri uri, final SettingsSetter<T> setter, T value)
+ throws SettingNotFoundException {
+ setter.set(getContentResolver(), uri.getLastPathSegment(), value);
+ }
+
+ private static <T> T get(final Uri uri, final SettingsGetter<T> getter)
+ throws SettingNotFoundException {
+ return getter.get(getContentResolver(), uri.getLastPathSegment());
+ }
+
+ private static void delete(final Uri uri) throws IllegalArgumentException {
+ getContentResolver().delete(uri, null, null);
+ }
+
+ private static ContentResolver getContentResolver() {
+ return InstrumentationRegistry.getTargetContext().getContentResolver();
+ }
+
+ private static class SessionCounters {
+ private final Map<Uri, Integer> mOpenSessions = new HashMap<>();
+
+ void open(final Uri uri) {
+ final Integer count = mOpenSessions.get(uri);
+ if (count == null) {
+ mOpenSessions.put(uri, 1);
+ return;
+ }
+ mOpenSessions.put(uri, count + 1);
+ Log.w(TAG, "Open nested session for " + uri, new Throwable());
+ }
+
+ void close(final Uri uri) {
+ final int count = mOpenSessions.get(uri);
+ if (count == 1) {
+ mOpenSessions.remove(uri);
+ return;
+ }
+ mOpenSessions.put(uri, count - 1);
+ Log.w(TAG, "Close nested session for " + uri, new Throwable());
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/Android.mk b/tests/framework/base/windowmanager/Android.mk
new file mode 100644
index 0000000..2111f19
--- /dev/null
+++ b/tests/framework/base/windowmanager/Android.mk
@@ -0,0 +1,42 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests optional
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, alertwindowservice/src)
+
+LOCAL_PACKAGE_NAME := CtsWindowManagerDeviceTestCases
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+ android-support-test \
+ platform-test-annotations \
+ cts-amwm-util
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
new file mode 100644
index 0000000..4bcd576
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.cts.wm">
+
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
+ <application android:label="CtsWindowManagerDeviceTestCases">
+ <uses-library android:name="android.test.runner"/>
+
+ <service
+ android:name="android.server.wm.TestLogService"
+ android:enabled="true"
+ android:exported="true">
+ </service>
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.server.cts.wm"
+ android:label="CTS tests of WindowManager">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
new file mode 100644
index 0000000..ff1bc97
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS WindowManager test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CtsWindowManagerDeviceTestCases.apk"/>
+ <option name="test-file-name" value="CtsDragAndDropSourceApp.apk"/>
+ <option name="test-file-name" value="CtsDragAndDropTargetApp.apk"/>
+ <option name="test-file-name" value="CtsDragAndDropTargetAppSdk23.apk"/>
+ <option name="test-file-name" value="CtsDeviceWindowFramesTestApp.apk"/>
+ <option name="test-file-name" value="CtsDeviceAlertWindowTestApp.apk"/>
+ <option name="test-file-name" value="CtsDeviceAlertWindowTestAppSdk25.apk"/>
+ <option name="test-file-name" value="CtsAlertWindowService.apk"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.server.cts.wm"/>
+ <option name="runtime-hint" value="8m"/>
+ </test>
+
+</configuration>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk b/tests/framework/base/windowmanager/alertwindowapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
rename to tests/framework/base/windowmanager/alertwindowapp/Android.mk
diff --git a/tests/framework/base/windowmanager/alertwindowapp/AndroidManifest.xml b/tests/framework/base/windowmanager/alertwindowapp/AndroidManifest.xml
new file mode 100755
index 0000000..446e2fa
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowapp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="android.server.wm.alertwindowapp">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application android:label="CtsAlertWindow">
+ <activity android:name=".AlertWindowTestActivity"
+ android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/AlertWindowTestActivity.java b/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/AlertWindowTestActivity.java
new file mode 100644
index 0000000..2185dd6
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowapp/src/android/server/wm/alertwindowapp/AlertWindowTestActivity.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowapp;
+
+import android.os.Bundle;
+import android.server.wm.alertwindowappsdk25.AlertWindowTestBaseActivity;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivity extends AlertWindowTestBaseActivity {
+ private static final int[] ALERT_WINDOW_TYPES = {
+ TYPE_PHONE,
+ TYPE_PRIORITY_PHONE,
+ TYPE_SYSTEM_ALERT,
+ TYPE_SYSTEM_ERROR,
+ TYPE_SYSTEM_OVERLAY,
+ TYPE_APPLICATION_OVERLAY
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ createAllAlertWindows(getPackageName());
+ }
+
+ @Override
+ protected int[] getAlertWindowTypes() {
+ return ALERT_WINDOW_TYPES;
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk b/tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
rename to tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/AndroidManifest.xml b/tests/framework/base/windowmanager/alertwindowappsdk25/AndroidManifest.xml
new file mode 100755
index 0000000..0ac1c91
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.wm.alertwindowappsdk25">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application android:label="CtsAlertWindowSdk25">
+ <activity android:name=".AlertWindowTestActivitySdk25"
+ android:exported="true" android:windowSoftInputMode="stateAlwaysVisible">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestActivitySdk25.java b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
new file mode 100644
index 0000000..47057f1
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestActivitySdk25.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowappsdk25;
+
+import android.os.Bundle;
+
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
+public class AlertWindowTestActivitySdk25 extends AlertWindowTestBaseActivity {
+ private static final int[] ALERT_WINDOW_TYPES = {
+ TYPE_PHONE,
+ TYPE_PRIORITY_PHONE,
+ TYPE_SYSTEM_ALERT,
+ TYPE_SYSTEM_ERROR,
+ TYPE_SYSTEM_OVERLAY
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ createAllAlertWindows(getPackageName());
+ }
+
+ @Override
+ protected int[] getAlertWindowTypes() {
+ return ALERT_WINDOW_TYPES;
+ }
+}
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java
new file mode 100644
index 0000000..021b886
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowappsdk25;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+
+public abstract class AlertWindowTestBaseActivity extends Activity {
+
+ protected void createAllAlertWindows(String windowName) {
+ final int[] alertWindowTypes = getAlertWindowTypes();
+ for (int type : alertWindowTypes) {
+ try {
+ createAlertWindow(type, windowName);
+ } catch (Exception e) {
+ Log.e("AlertWindowTestBaseActivity", "Can't create type=" + type, e);
+ }
+ }
+ }
+
+ protected void createAlertWindow(int type) {
+ createAlertWindow(type, getPackageName());
+ }
+
+ protected void createAlertWindow(int type, String windowName) {
+ if (!isSystemAlertWindowType(type)) {
+ throw new IllegalArgumentException("Well...you are not an alert window type=" + type);
+ }
+
+ final Point size = new Point();
+ final WindowManager wm = getSystemService(WindowManager.class);
+ wm.getDefaultDisplay().getSize(size);
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ type, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
+ params.width = size.x / 3;
+ params.height = size.y / 3;
+ params.gravity = TOP | LEFT;
+ params.setTitle(windowName);
+
+ final TextView view = new TextView(this);
+ view.setText(windowName + " type=" + type);
+ view.setBackgroundColor(Color.RED);
+ wm.addView(view, params);
+ }
+
+ private boolean isSystemAlertWindowType(int type) {
+ final int[] alertWindowTypes = getAlertWindowTypes();
+ for (int current : alertWindowTypes) {
+ if (current == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected abstract int[] getAlertWindowTypes();
+}
diff --git a/tests/framework/base/windowmanager/alertwindowservice/Android.mk b/tests/framework/base/windowmanager/alertwindowservice/Android.mk
new file mode 100644
index 0000000..c012a65
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-device-util \
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsAlertWindowService
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/alertwindowservice/AndroidManifest.xml b/tests/framework/base/windowmanager/alertwindowservice/AndroidManifest.xml
new file mode 100644
index 0000000..76c30bd
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.wm.alertwindowservice">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name=".AlertWindowService"
+ android:exported="true"/>
+ </application>
+</manifest>
diff --git a/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.java b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.java
new file mode 100644
index 0000000..d531bbd
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.alertwindowservice;
+
+import static android.graphics.Color.BLUE;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import java.util.LinkedList;
+
+/** Service for creating and managing alert windows. */
+public final class AlertWindowService extends Service {
+
+ private static final String TAG = "AlertWindowService";
+ private static final boolean DEBUG = false;
+
+ public static final String PACKAGE_NAME = "android.server.wm.alertwindowservice";
+ public static final String EXTRA_MESSENGER = "messenger";
+
+ public static final int MSG_ADD_ALERT_WINDOW = 1;
+ public static final int MSG_REMOVE_ALERT_WINDOW = 2;
+ public static final int MSG_REMOVE_ALL_ALERT_WINDOWS = 3;
+
+ public static final int MSG_ON_ALERT_WINDOW_ADDED = 4;
+ public static final int MSG_ON_ALERT_WINDOW_REMOVED = 5;
+
+ private LinkedList<View> mAlertWindows = new LinkedList<>();
+
+ private Messenger mOutgoingMessenger = null;
+ private final Messenger mIncomingMessenger = new Messenger(new IncomingHandler());
+
+ private class IncomingHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ADD_ALERT_WINDOW:
+ addAlertWindow();
+ break;
+ case MSG_REMOVE_ALERT_WINDOW:
+ removeAlertWindow();
+ break;
+ case MSG_REMOVE_ALL_ALERT_WINDOWS:
+ removeAllAlertWindows();
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ private void addAlertWindow() {
+ final Point size = new Point();
+ final WindowManager wm = getSystemService(WindowManager.class);
+ wm.getDefaultDisplay().getSize(size);
+
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE);
+ params.width = size.x / 3;
+ params.height = size.y / 3;
+ params.gravity = TOP | LEFT;
+
+ final TextView view = new TextView(this);
+ view.setText("AlertWindowService" + mAlertWindows.size());
+ view.setBackgroundColor(BLUE);
+ wm.addView(view, params);
+ mAlertWindows.add(view);
+
+ if (DEBUG) Log.e(TAG, "addAlertWindow " + mAlertWindows.size());
+ if (mOutgoingMessenger != null) {
+ try {
+ mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_ADDED));
+ } catch (RemoteException e) {
+
+ }
+ }
+ }
+
+ private void removeAlertWindow() {
+ if (mAlertWindows.size() == 0) {
+ return;
+ }
+ final WindowManager wm = getSystemService(WindowManager.class);
+ wm.removeView(mAlertWindows.pop());
+
+ if (DEBUG) Log.e(TAG, "removeAlertWindow " + mAlertWindows.size());
+ if (mOutgoingMessenger != null) {
+ try {
+ mOutgoingMessenger.send(Message.obtain(null, MSG_ON_ALERT_WINDOW_REMOVED));
+ } catch (RemoteException e) {
+
+ }
+ }
+ }
+
+ private void removeAllAlertWindows() {
+ while (mAlertWindows.size() > 0) {
+ removeAlertWindow();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (DEBUG) Log.e(TAG, "onBind");
+ mOutgoingMessenger = intent.getParcelableExtra(EXTRA_MESSENGER);
+ return mIncomingMessenger.getBinder();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (DEBUG) Log.e(TAG, "onUnbind");
+ removeAllAlertWindows();
+ return super.onUnbind(intent);
+ }
+}
diff --git a/tests/framework/base/windowmanager/dndsourceapp/Android.mk b/tests/framework/base/windowmanager/dndsourceapp/Android.mk
new file mode 100644
index 0000000..7cfd03f
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../src/android/server/wm/TestLogClient.java
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropSourceApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
new file mode 100644
index 0000000..4c8f0bb
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.wm.dndsourceapp">
+ <application android:label="CtsDnDSource">
+ <activity android:name="android.server.wm.dndsourceapp.DragSource">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <provider android:name="android.server.wm.dndsourceapp.DragSourceContentProvider"
+ android:authorities="android.server.wm.dndsource.contentprovider"
+ android:grantUriPermissions="true"/>
+ </application>
+</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/res/layout/source_activity.xml b/tests/framework/base/windowmanager/dndsourceapp/res/layout/source_activity.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/dndsourceapp/res/layout/source_activity.xml
rename to tests/framework/base/windowmanager/dndsourceapp/res/layout/source_activity.xml
diff --git a/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSource.java b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSource.java
new file mode 100644
index 0000000..9c7ed7b
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSource.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.dndsourceapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.FileUriExposedException;
+import android.os.PersistableBundle;
+import android.server.wm.TestLogClient;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+
+import java.io.File;
+
+public class DragSource extends Activity{
+ private static final String RESULT_KEY_START_DRAG = "START_DRAG";
+ private static final String RESULT_KEY_DETAILS = "DETAILS";
+ private static final String RESULT_OK = "OK";
+ private static final String RESULT_EXCEPTION = "Exception";
+
+ private static final String URI_PREFIX =
+ "content://" + DragSourceContentProvider.AUTHORITY + "/data";
+
+ private static final String MAGIC_VALUE = "42";
+ private static final long TIMEOUT_CANCEL = 150;
+
+ private TextView mTextView;
+ private TestLogClient mLogClient;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLogClient = new TestLogClient(this, getIntent().getStringExtra("logtag"));
+
+ View view = getLayoutInflater().inflate(R.layout.source_activity, null);
+ setContentView(view);
+
+ final Uri plainUri = Uri.parse(URI_PREFIX + "/" + MAGIC_VALUE);
+
+ setUpDragSource("disallow_global", plainUri, 0);
+ setUpDragSource("cancel_soon", plainUri, View.DRAG_FLAG_GLOBAL);
+
+ setUpDragSource("grant_none", plainUri, View.DRAG_FLAG_GLOBAL);
+ setUpDragSource("grant_read", plainUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
+ setUpDragSource("grant_write", plainUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_WRITE);
+ setUpDragSource("grant_read_persistable", plainUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+ View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION);
+
+ final Uri prefixUri = Uri.parse(URI_PREFIX);
+
+ setUpDragSource("grant_read_prefix", prefixUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ |
+ View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION);
+ setUpDragSource("grant_read_noprefix", prefixUri,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
+
+ final Uri fileUri = Uri.fromFile(new File("/sdcard/sample.jpg"));
+
+ setUpDragSource("file_local", fileUri, 0);
+ setUpDragSource("file_global", fileUri, View.DRAG_FLAG_GLOBAL);
+ }
+
+ private void setUpDragSource(String mode, final Uri uri, final int flags) {
+ if (!mode.equals(getIntent().getStringExtra("mode"))) {
+ return;
+ }
+ mTextView = (TextView) findViewById(R.id.drag_source);
+ mTextView.setText(mode);
+ mTextView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ try {
+ final ClipDescription clipDescription = new ClipDescription("", new String[] {
+ ClipDescription.MIMETYPE_TEXT_URILIST });
+ PersistableBundle extras = new PersistableBundle(1);
+ extras.putString("extraKey", "extraValue");
+ clipDescription.setExtras(extras);
+ final ClipData clipData = new ClipData(clipDescription, new ClipData.Item(uri));
+ v.startDragAndDrop(
+ clipData,
+ new View.DragShadowBuilder(v),
+ null,
+ flags);
+ logResult(RESULT_KEY_START_DRAG, RESULT_OK);
+ } catch (FileUriExposedException e) {
+ logResult(RESULT_KEY_DETAILS, e.getMessage());
+ logResult(RESULT_KEY_START_DRAG, RESULT_EXCEPTION);
+ }
+ if (mode.equals("cancel_soon")) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ v.cancelDragAndDrop();
+ }
+ }, TIMEOUT_CANCEL);
+ }
+ return true;
+ }
+ });
+ }
+
+ private void logResult(String key, String value) {
+ mLogClient.record(key, value);
+ mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
+ }
+}
diff --git a/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceContentProvider.java b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceContentProvider.java
new file mode 100644
index 0000000..1ec1e58
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceContentProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.dndsourceapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class DragSourceContentProvider extends ContentProvider {
+
+ public static final String AUTHORITY = "android.server.wm.dndsource.contentprovider";
+
+ private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final int URI_DATA = 1;
+
+ static {
+ sMatcher.addURI(AUTHORITY, "data/#", URI_DATA);
+ }
+
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ switch (sMatcher.match(uri)) {
+ case URI_DATA:
+ return new DragSourceCursor(uri.getLastPathSegment());
+ }
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceCursor.java b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceCursor.java
new file mode 100644
index 0000000..468842f
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndsourceapp/src/android/server/wm/dndsourceapp/DragSourceCursor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.dndsourceapp;
+
+import android.database.AbstractCursor;
+
+public class DragSourceCursor extends AbstractCursor {
+ private static final String COLUMN_KEY = "key";
+
+ private final String mValue;
+
+ public DragSourceCursor(String value) {
+ mValue = value;
+ }
+
+ @Override
+ public int getCount() {
+ return 1;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return new String[] {COLUMN_KEY};
+ }
+
+ @Override
+ public String getString(int column) {
+ if (getPosition() != 0) {
+ throw new IllegalArgumentException("Incorrect position: " + getPosition());
+ }
+ if (column != 0) {
+ throw new IllegalArgumentException("Incorrect column: " + column);
+ }
+ return mValue;
+ }
+
+ @Override
+ public short getShort(int column) {
+ return 0;
+ }
+
+ @Override
+ public int getInt(int column) {
+ return 0;
+ }
+
+ @Override
+ public long getLong(int column) {
+ return 0;
+ }
+
+ @Override
+ public float getFloat(int column) {
+ return 0;
+ }
+
+ @Override
+ public double getDouble(int column) {
+ return 0;
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return false;
+ }
+}
diff --git a/tests/framework/base/windowmanager/dndtargetapp/Android.mk b/tests/framework/base/windowmanager/dndtargetapp/Android.mk
new file mode 100644
index 0000000..d291df4
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetapp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../src/android/server/wm/TestLogClient.java
+
+LOCAL_SDK_VERSION := current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropTargetApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
new file mode 100644
index 0000000..33c7a0f
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.wm.dndtargetapp">
+ <application android:label="CtsDnDTarget">
+ <activity android:name="android.server.wm.dndtargetapp.DropTarget">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/res/layout/target_activity.xml b/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetapp/res/layout/target_activity.xml
rename to tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
diff --git a/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
new file mode 100644
index 0000000..0500ef4
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.dndtargetapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.server.wm.TestLogClient;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class DropTarget extends Activity {
+ private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+ private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
+ private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+ private static final String RESULT_KEY_DROP_RESULT = "DROP";
+ private static final String RESULT_KEY_DETAILS = "DETAILS";
+ private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
+ private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
+ private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
+ private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
+ private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
+
+ public static final String RESULT_OK = "OK";
+ public static final String RESULT_EXCEPTION = "Exception";
+ public static final String RESULT_MISSING = "MISSING";
+ public static final String RESULT_LEAKING = "LEAKING";
+
+ protected static final String MAGIC_VALUE = "42";
+
+ private TextView mTextView;
+ private TestLogClient mLogClient;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLogClient = new TestLogClient(this, getIntent().getStringExtra("logtag"));
+
+ View view = getLayoutInflater().inflate(R.layout.target_activity, null);
+ setContentView(view);
+
+ setUpDropTarget("request_none", new OnDragUriReadListener(false));
+ setUpDropTarget("request_read", new OnDragUriReadListener());
+ setUpDropTarget("request_write", new OnDragUriWriteListener());
+ setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
+ setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
+ }
+
+ private void setUpDropTarget(String mode, OnDragUriListener listener) {
+ if (!mode.equals(getIntent().getStringExtra("mode"))) {
+ return;
+ }
+ mTextView = (TextView)findViewById(R.id.drag_target);
+ mTextView.setText(mode);
+ mTextView.setOnDragListener(listener);
+ }
+
+ private String checkExtraValue(DragEvent event) {
+ PersistableBundle extras = event.getClipDescription().getExtras();
+ if (extras == null) {
+ return "Null";
+ }
+
+ final String value = extras.getString("extraKey");
+ if ("extraValue".equals(value)) {
+ return RESULT_OK;
+ }
+ return value;
+ }
+
+ private void logResult(String key, String value) {
+ mLogClient.record(key, value);
+ mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
+ }
+
+ private abstract class OnDragUriListener implements View.OnDragListener {
+ private final boolean requestPermissions;
+
+ public OnDragUriListener(boolean requestPermissions) {
+ this.requestPermissions = requestPermissions;
+ }
+
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ checkDragEvent(event);
+
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+ logResult(RESULT_KEY_EXTRAS, checkExtraValue(event));
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENTERED:
+ return true;
+
+ case DragEvent.ACTION_DRAG_LOCATION:
+ return true;
+
+ case DragEvent.ACTION_DRAG_EXITED:
+ return true;
+
+ case DragEvent.ACTION_DROP:
+ // Try accessing the Uri without the permissions grant.
+ accessContent(event, RESULT_KEY_ACCESS_BEFORE, false);
+
+ // Try accessing the Uri with the permission grant (if required);
+ accessContent(event, RESULT_KEY_DROP_RESULT, requestPermissions);
+
+ // Try accessing the Uri after the permissions have been released.
+ accessContent(event, RESULT_KEY_ACCESS_AFTER, false);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENDED:
+ logResult(RESULT_KEY_DRAG_ENDED, RESULT_OK);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ private void accessContent(DragEvent event, String resultKey, boolean requestPermissions) {
+ String result;
+ try {
+ result = processDrop(event, requestPermissions);
+ } catch (SecurityException e) {
+ result = RESULT_EXCEPTION;
+ if (resultKey.equals(RESULT_KEY_DROP_RESULT)) {
+ logResult(RESULT_KEY_DETAILS, e.getMessage());
+ }
+ }
+ logResult(resultKey, result);
+ }
+
+ private String processDrop(DragEvent event, boolean requestPermissions) {
+ final ClipData clipData = event.getClipData();
+ if (clipData == null) {
+ return "Null ClipData";
+ }
+ if (clipData.getItemCount() == 0) {
+ return "Empty ClipData";
+ }
+ ClipData.Item item = clipData.getItemAt(0);
+ if (item == null) {
+ return "Null ClipData.Item";
+ }
+ Uri uri = item.getUri();
+ if (uri == null) {
+ return "Null Uri";
+ }
+
+ DragAndDropPermissions permissions = null;
+ if (requestPermissions) {
+ permissions = requestDragAndDropPermissions(event);
+ if (permissions == null) {
+ return "Null DragAndDropPermissions";
+ }
+ }
+
+ try {
+ return processUri(uri);
+ } finally {
+ if (permissions != null) {
+ permissions.release();
+ }
+ }
+ }
+
+ abstract protected String processUri(Uri uri);
+ }
+
+ private void checkDragEvent(DragEvent event) {
+ final int action = event.getAction();
+
+ // ClipData should be available for ACTION_DROP only.
+ final ClipData clipData = event.getClipData();
+ if (action == DragEvent.ACTION_DROP) {
+ if (clipData == null) {
+ logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
+ }
+ } else {
+ if (clipData != null) {
+ logResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_LEAKING + action);
+ }
+ }
+
+ // ClipDescription should be always available except for ACTION_DRAG_ENDED.
+ final ClipDescription clipDescription = event.getClipDescription();
+ if (action != DragEvent.ACTION_DRAG_ENDED) {
+ if (clipDescription == null) {
+ logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING + action);
+ }
+ } else {
+ if (clipDescription != null) {
+ logResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_LEAKING);
+ }
+ }
+
+ // Local state should be always null for cross-app drags.
+ final Object localState = event.getLocalState();
+ if (localState != null) {
+ logResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_LEAKING + action);
+ }
+ }
+
+ private class OnDragUriReadListener extends OnDragUriListener {
+ OnDragUriReadListener(boolean requestPermissions) {
+ super(requestPermissions);
+ }
+
+ OnDragUriReadListener() {
+ super(true);
+ }
+
+ protected String processUri(Uri uri) {
+ return checkQueryResult(uri, MAGIC_VALUE);
+ }
+
+ protected String checkQueryResult(Uri uri, String expectedValue) {
+ Cursor cursor = null;
+ try {
+ cursor = getContentResolver().query(uri, null, null, null, null);
+ if (cursor == null) {
+ return "Null Cursor";
+ }
+ cursor.moveToPosition(0);
+ String value = cursor.getString(0);
+ if (!expectedValue.equals(value)) {
+ return "Wrong value: " + value;
+ }
+ return RESULT_OK;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ private class OnDragUriWriteListener extends OnDragUriListener {
+ OnDragUriWriteListener() {
+ super(true);
+ }
+
+ protected String processUri(Uri uri) {
+ ContentValues values = new ContentValues();
+ values.put("key", 100);
+ getContentResolver().update(uri, values, null, null);
+ return RESULT_OK;
+ }
+ }
+
+ private class OnDragUriReadPrefixListener extends OnDragUriReadListener {
+ @Override
+ protected String processUri(Uri uri) {
+ final String result1 = queryPrefixed(uri, "1");
+ if (!result1.equals(RESULT_OK)) {
+ return result1;
+ }
+ final String result2 = queryPrefixed(uri, "2");
+ if (!result2.equals(RESULT_OK)) {
+ return result2;
+ }
+ return queryPrefixed(uri, "3");
+ }
+
+ private String queryPrefixed(Uri uri, String selector) {
+ final Uri prefixedUri = Uri.parse(uri.toString() + "/" + selector);
+ return checkQueryResult(prefixedUri, selector);
+ }
+ }
+
+ private class OnDragUriTakePersistableListener extends OnDragUriListener {
+ OnDragUriTakePersistableListener() {
+ super(true);
+ }
+
+ @Override
+ protected String processUri(Uri uri) {
+ getContentResolver().takePersistableUriPermission(
+ uri, View.DRAG_FLAG_GLOBAL_URI_READ);
+ getContentResolver().releasePersistableUriPermission(
+ uri, View.DRAG_FLAG_GLOBAL_URI_READ);
+ return RESULT_OK;
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/Android.mk b/tests/framework/base/windowmanager/dndtargetappsdk23/Android.mk
new file mode 100644
index 0000000..59f4ab1
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ ../src/android/server/wm/TestLogClient.java
+
+LOCAL_SDK_VERSION := 23
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDragAndDropTargetAppSdk23
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
new file mode 100644
index 0000000..d10a548
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.wm.dndtargetappsdk23">
+ <application android:label="CtsDnDTarget">
+ <activity android:name="android.server.wm.dndtargetappsdk23.DropTarget">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml b/tests/framework/base/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml
rename to tests/framework/base/windowmanager/dndtargetappsdk23/res/layout/target_activity.xml
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/src/android/server/wm/dndtargetappsdk23/DropTarget.java b/tests/framework/base/windowmanager/dndtargetappsdk23/src/android/server/wm/dndtargetappsdk23/DropTarget.java
new file mode 100644
index 0000000..93a8659
--- /dev/null
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/src/android/server/wm/dndtargetappsdk23/DropTarget.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.dndtargetappsdk23;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.server.wm.TestLogClient;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * This application is compiled against SDK 23 and used to verify that apps targeting SDK 23 and
+ * below do not receive global drags.
+ */
+public class DropTarget extends Activity {
+ private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+ private static final String RESULT_KEY_DROP_RESULT = "DROP";
+
+ public static final String RESULT_OK = "OK";
+
+ private TextView mTextView;
+ private TestLogClient mLogClient;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ View view = getLayoutInflater().inflate(R.layout.target_activity, null);
+ setContentView(view);
+
+ mTextView = (TextView) findViewById(R.id.drag_target);
+ mTextView.setOnDragListener(new OnDragListener());
+
+ mLogClient = new TestLogClient(this, getIntent().getStringExtra("logtag"));
+ }
+
+ private void logResult(String key, String value) {
+ mLogClient.record(key, value);
+ mTextView.setText(key + "=" + value);
+ }
+
+ private class OnDragListener implements View.OnDragListener {
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENTERED:
+ return true;
+
+ case DragEvent.ACTION_DRAG_LOCATION:
+ return true;
+
+ case DragEvent.ACTION_DRAG_EXITED:
+ return true;
+
+ case DragEvent.ACTION_DROP:
+ logResult(RESULT_KEY_DROP_RESULT, RESULT_OK);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENDED:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk b/tests/framework/base/windowmanager/frametestapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk
rename to tests/framework/base/windowmanager/frametestapp/Android.mk
diff --git a/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml b/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml
new file mode 100755
index 0000000..90dd8f0
--- /dev/null
+++ b/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.server.wm.frametestapp">
+
+ <application android:theme="@android:style/Theme.Material">
+ <activity
+ android:name=".DialogTestActivity"
+ android:exported="true" />
+ <activity
+ android:name=".MovingChildTestActivity"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java
new file mode 100644
index 0000000..c0d8ffd
--- /dev/null
+++ b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.frametestapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.view.Window;
+import android.view.Gravity;
+
+public class DialogTestActivity extends Activity {
+
+ private static final String DIALOG_WINDOW_NAME = "TestDialog";
+
+ /**
+ * Extra key for test case name.
+ * @see android.server.wm.ParentChildTestBase#EXTRA_TEST_CASE
+ */
+ private static final String EXTRA_TEST_CASE = "test-case";
+
+ private AlertDialog mDialog;
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+ protected void onStop() {
+ super.onStop();
+ mDialog.dismiss();
+ }
+
+ protected void onResume() {
+ super.onResume();
+ setupTest(getIntent());
+ }
+
+ private void setupTest(Intent intent) {
+ final String testCase = intent.getStringExtra(EXTRA_TEST_CASE);
+ switch (testCase) {
+ case "MatchParent":
+ testMatchParent();
+ break;
+ case "MatchParentLayoutInOverscan":
+ testMatchParentLayoutInOverscan();
+ break;
+ case "ExplicitSize":
+ testExplicitSize();
+ break;
+ case "ExplicitSizeTopLeftGravity":
+ testExplicitSizeTopLeftGravity();
+ break;
+ case "ExplicitSizeBottomRightGravity":
+ testExplicitSizeBottomRightGravity();
+ break;
+ case "OversizedDimensions":
+ testOversizedDimensions();
+ break;
+ case "OversizedDimensionsNoLimits":
+ testOversizedDimensionsNoLimits();
+ break;
+ case "ExplicitPositionMatchParent":
+ testExplicitPositionMatchParent();
+ break;
+ case "ExplicitPositionMatchParentNoLimits":
+ testExplicitPositionMatchParentNoLimits();
+ break;
+ case "NoFocus":
+ testNoFocus();
+ break;
+ case "WithMargins":
+ testWithMargins();
+ break;
+ default:
+ break;
+ }
+ }
+
+ interface DialogLayoutParamsTest {
+ void doSetup(WindowManager.LayoutParams p);
+ }
+
+ private void doLayoutParamTest(DialogLayoutParamsTest t) {
+ mDialog = new AlertDialog.Builder(this).create();
+
+ mDialog.setMessage("Testing is fun!");
+ mDialog.setTitle(DIALOG_WINDOW_NAME);
+ mDialog.create();
+
+ Window w = mDialog.getWindow();
+ final WindowManager.LayoutParams params = w.getAttributes();
+ t.doSetup(params);
+ w.setAttributes(params);
+
+ mDialog.show();
+ }
+
+ private void testMatchParent() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ });
+ }
+
+ private void testMatchParentLayoutInOverscan() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
+ });
+ }
+
+ private void testExplicitSize() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = 200;
+ params.height = 200;
+ });
+ }
+
+ private void testExplicitSizeTopLeftGravity() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = 200;
+ params.height = 200;
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ });
+ }
+
+ private void testExplicitSizeBottomRightGravity() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = 200;
+ params.height = 200;
+ params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ });
+ }
+
+ private void testOversizedDimensions() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = 100000;
+ params.height = 100000;
+ });
+ }
+
+ private void testOversizedDimensionsNoLimits() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = 5000;
+ params.height = 5000;
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ params.gravity = Gravity.LEFT | Gravity.TOP;
+ });
+ }
+
+ private void testExplicitPositionMatchParent() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ params.x = 100;
+ params.y = 100;
+ });
+ }
+
+ private void testExplicitPositionMatchParentNoLimits() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ params.gravity = Gravity.LEFT | Gravity.TOP;
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ params.x = 100;
+ params.y = 100;
+ });
+ }
+
+ private void testNoFocus() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ });
+ }
+
+ private void testWithMargins() {
+ doLayoutParamTest((WindowManager.LayoutParams params) -> {
+ params.gravity = Gravity.LEFT | Gravity.TOP;
+ params.horizontalMargin = .25f;
+ params.verticalMargin = .35f;
+ params.width = 200;
+ params.height = 200;
+ params.x = 0;
+ params.y = 0;
+ });
+ }
+}
diff --git a/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java
new file mode 100644
index 0000000..78f8fa0
--- /dev/null
+++ b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.frametestapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.Space;
+
+// This activity will parent a Child to the main window, and then move
+// the main window around. We can use this to verify the Child
+// is properly updated.
+public class MovingChildTestActivity extends Activity {
+
+ private static final String POPUP_WINDOW_NAME = "ChildWindow";
+
+ private Space mView;
+ private int mX = 0;
+ private int mY = 0;
+
+ final Runnable moveWindow = new Runnable() {
+ @Override
+ public void run() {
+ final Window w = getWindow();
+ final WindowManager.LayoutParams attribs = w.getAttributes();
+ attribs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ attribs.x = mX % 1000;
+ attribs.y = mY % 1000;
+ w.setAttributes(attribs);
+ mX += 5;
+ mY += 5;
+ mView.postDelayed(this, 50);
+ }
+ };
+
+ final Runnable makeChild = new Runnable() {
+ @Override
+ public void run() {
+ Button b = new Button(MovingChildTestActivity.this);
+ WindowManager.LayoutParams p = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_PANEL);
+ p.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ p.x = 0;
+ p.y = 0;
+ p.token = mView.getWindowToken();
+ p.setTitle(POPUP_WINDOW_NAME);
+
+ ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).addView(b, p);
+
+ mView.postDelayed(moveWindow, 50);
+ }
+ };
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final LayoutParams p = new LayoutParams(100, 100);
+ final Window w = getWindow();
+ w.setLayout(100, 100);
+ mView = new Space(this);
+
+ setContentView(mView, p);
+ mView.post(makeChild);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
new file mode 100644
index 0000000..7e4bdbc
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE_PRE_26;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.content.Context.BIND_ALLOW_OOM_MANAGEMENT;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.alertwindowservice.AlertWindowService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.ToIntFunction;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.AlertWindowsImportanceTests
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class AlertWindowsImportanceTests {
+
+ private static final String TAG = "AlertWindowsTests";
+
+ private static final boolean DEBUG = false;
+ private static final long WAIT_TIME_MS = 2 * 1000;
+
+ private static final String SDK25_PACKAGE_NAME = "android.server.wm.alertwindowappsdk25";
+
+ private Messenger mService;
+ private String mServicePackageName;
+
+ private ActivityManager mAm;
+ private ActivityManager mAm25; // ActivityManager created for an SDK 25 app context.
+
+ private final Messenger mMessenger = new Messenger(new IncomingHandler(Looper.getMainLooper()));
+ private final Object mAddedLock = new Object();
+ private final Object mRemoveLock = new Object();
+
+ @Before
+ public void setUp() throws Exception {
+ if (DEBUG) Log.e(TAG, "setUp");
+ final Context context = InstrumentationRegistry.getTargetContext();
+
+ mAm = context.getSystemService(ActivityManager.class);
+ mAm25 = context.createPackageContext(SDK25_PACKAGE_NAME, 0)
+ .getSystemService(ActivityManager.class);
+
+ final Intent intent = new Intent()
+ .setClassName(AlertWindowService.PACKAGE_NAME, AlertWindowService.class.getName())
+ .putExtra(AlertWindowService.EXTRA_MESSENGER, mMessenger);
+ // Needs to be both BIND_NOT_FOREGROUND and BIND_ALLOW_OOM_MANAGEMENT to avoid the binding
+ // to this instrumentation test from increasing its importance.
+ context.bindService(intent, mConnection,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_ALLOW_OOM_MANAGEMENT);
+ synchronized (mConnection) {
+ // Wait for alert window service to be connection before processing.
+ mConnection.wait(WAIT_TIME_MS);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (DEBUG) Log.e(TAG, "tearDown");
+ if (mService != null) {
+ mService.send(Message.obtain(null, AlertWindowService.MSG_REMOVE_ALL_ALERT_WINDOWS));
+ }
+ final Context context = InstrumentationRegistry.getTargetContext();
+ context.unbindService(mConnection);
+ mAm = null;
+ mAm25 = null;
+ }
+
+ @Test
+ public void testAlertWindowOomAdj() throws Exception {
+ // Alert windows are always hidden when running in VR.
+ if (isRunningInVR()) {
+ return;
+ }
+ setAlertWindowPermission(true /* allow */);
+
+ assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+
+ // TODO AM.getUidImportance() sometimes return a different value from what
+ // getPackageImportance() returns... b/37950472
+ // assertUidImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+
+ addAlertWindow();
+ // Process importance should be increased to visible when the service has an alert window.
+ assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+ addAlertWindow();
+ assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+ setAlertWindowPermission(false /* allow */);
+ // Process importance should no longer be visible since its alert windows are not allowed to
+ // be visible.
+ assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+
+ setAlertWindowPermission(true /* allow */);
+ // They can show again so importance should be visible again.
+ assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+ removeAlertWindow();
+ assertPackageImportance(IMPORTANCE_VISIBLE, IMPORTANCE_VISIBLE);
+
+ removeAlertWindow();
+ // Process importance should no longer be visible when the service no longer as alert
+ // windows.
+ assertPackageImportance(IMPORTANCE_PERCEPTIBLE, IMPORTANCE_PERCEPTIBLE_PRE_26);
+ }
+
+ private void addAlertWindow() throws Exception {
+ mService.send(Message.obtain(null, AlertWindowService.MSG_ADD_ALERT_WINDOW));
+ synchronized (mAddedLock) {
+ // Wait for window addition confirmation before proceeding.
+ mAddedLock.wait(WAIT_TIME_MS);
+ }
+ }
+
+ private void removeAlertWindow() throws Exception {
+ mService.send(Message.obtain(null, AlertWindowService.MSG_REMOVE_ALERT_WINDOW));
+ synchronized (mRemoveLock) {
+ // Wait for window removal confirmation before proceeding.
+ mRemoveLock.wait(WAIT_TIME_MS);
+ }
+ }
+
+ private void setAlertWindowPermission(boolean allow) throws Exception {
+ final String cmd = "appops set " + mServicePackageName
+ + " android:system_alert_window " + (allow ? "allow" : "deny");
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
+ }
+
+ private void assertImportance(ToIntFunction<ActivityManager> apiCaller,
+ int expectedForO, int expectedForPreO) throws Exception {
+ final long TIMEOUT = SystemClock.uptimeMillis() + TimeUnit.SECONDS.toMillis(30);
+ int actual;
+
+ do {
+ // TODO: We should try to use ActivityManagerTest.UidImportanceListener here to listen
+ // for changes in the uid importance. However, the way it is currently structured
+ // doesn't really work for this use case right now...
+ Thread.sleep(500);
+ actual = apiCaller.applyAsInt(mAm);
+ } while (actual != expectedForO && (SystemClock.uptimeMillis() < TIMEOUT));
+
+ assertEquals(expectedForO, actual);
+
+ // Check the result for pre-O apps.
+ assertEquals(expectedForPreO, apiCaller.applyAsInt(mAm25));
+ }
+
+ /**
+ * Make sure {@link ActivityManager#getPackageImportance} returns the expected value.
+ */
+ private void assertPackageImportance(int expectedForO, int expectedForPreO) throws Exception {
+ assertImportance(am -> am.getPackageImportance(mServicePackageName),
+ expectedForO, expectedForPreO);
+ }
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Log.e(TAG, "onServiceConnected");
+ mService = new Messenger(service);
+ mServicePackageName = name.getPackageName();
+ synchronized (mConnection) {
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Log.e(TAG, "onServiceDisconnected");
+ mService = null;
+ mServicePackageName = null;
+ }
+ };
+
+ private class IncomingHandler extends Handler {
+
+ IncomingHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AlertWindowService.MSG_ON_ALERT_WINDOW_ADDED:
+ synchronized (mAddedLock) {
+ if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_ADDED");
+ mAddedLock.notifyAll();
+ }
+ break;
+ case AlertWindowService.MSG_ON_ALERT_WINDOW_REMOVED:
+ synchronized (mRemoveLock) {
+ if (DEBUG) Log.e(TAG, "MSG_ON_ALERT_WINDOW_REMOVED");
+ mRemoveLock.notifyAll();
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ private boolean isRunningInVR() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ if ((context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
+ == Configuration.UI_MODE_TYPE_VR_HEADSET) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
new file mode 100644
index 0000000..5b3ddb8
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.WaitForValidActivityState;
+import android.server.am.WindowManagerState;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.AlertWindowsTests
+ */
+@Presubmit
+public class AlertWindowsTests extends ActivityManagerTestBase {
+
+ private static final ComponentName TEST_ACTIVITY = ComponentName.createRelative(
+ "android.server.wm.alertwindowapp", ".AlertWindowTestActivity");
+ private static final ComponentName SDK25_TEST_ACTIVITY = ComponentName.createRelative(
+ "android.server.wm.alertwindowappsdk25", ".AlertWindowTestActivitySdk25");
+
+ // From WindowManager.java
+ private static final int TYPE_BASE_APPLICATION = 1;
+ private static final int FIRST_SYSTEM_WINDOW = 2000;
+
+ private static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW + 2;
+ private static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3;
+ private static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6;
+ private static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7;
+ private static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10;
+ private static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
+
+ private static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
+ private static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11;
+ private static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW + 19;
+
+ private final List<Integer> mAlertWindowTypes = Arrays.asList(
+ TYPE_PHONE,
+ TYPE_PRIORITY_PHONE,
+ TYPE_SYSTEM_ALERT,
+ TYPE_SYSTEM_ERROR,
+ TYPE_SYSTEM_OVERLAY,
+ TYPE_APPLICATION_OVERLAY);
+ private final List<Integer> mSystemWindowTypes = Arrays.asList(
+ TYPE_STATUS_BAR,
+ TYPE_INPUT_METHOD,
+ TYPE_NAVIGATION_BAR);
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ setAlertWindowPermission(TEST_ACTIVITY, false);
+ setAlertWindowPermission(SDK25_TEST_ACTIVITY, false);
+ stopTestPackage(TEST_ACTIVITY);
+ stopTestPackage(SDK25_TEST_ACTIVITY);
+ }
+
+ @Test
+ public void testAlertWindowAllowed() throws Exception {
+ runAlertWindowTest(TEST_ACTIVITY, true /* hasAlertWindowPermission */,
+ true /* atLeastO */);
+ }
+
+ @Test
+ public void testAlertWindowDisallowed() throws Exception {
+ runAlertWindowTest(TEST_ACTIVITY, false /* hasAlertWindowPermission */,
+ true /* atLeastO */);
+ }
+
+ @Test
+ public void testAlertWindowAllowedSdk25() throws Exception {
+ runAlertWindowTest(SDK25_TEST_ACTIVITY, true /* hasAlertWindowPermission */,
+ false /* atLeastO */);
+ }
+
+ @Test
+ public void testAlertWindowDisallowedSdk25() throws Exception {
+ runAlertWindowTest(SDK25_TEST_ACTIVITY, false /* hasAlertWindowPermission */,
+ false /* atLeastO */);
+ }
+
+ private void runAlertWindowTest(final ComponentName activityName,
+ final boolean hasAlertWindowPermission, final boolean atLeastO) throws Exception {
+ setAlertWindowPermission(activityName, hasAlertWindowPermission);
+
+ executeShellCommand(getAmStartCmd(activityName));
+ mAmWmState.computeState(new WaitForValidActivityState(activityName));
+ mAmWmState.assertVisibility(activityName, true);
+
+ assertAlertWindows(activityName, hasAlertWindowPermission, atLeastO);
+ }
+
+ private void assertAlertWindows(final ComponentName activityName,
+ final boolean hasAlertWindowPermission, final boolean atLeastO) {
+ final String packageName = activityName.getPackageName();
+ final WindowManagerState wMState = mAmWmState.getWmState();
+
+ final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList<>();
+ wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
+
+ if (!hasAlertWindowPermission) {
+ assertTrue("Should be empty alertWindows=" + alertWindows, alertWindows.isEmpty());
+ return;
+ }
+
+ if (atLeastO) {
+ // Assert that only TYPE_APPLICATION_OVERLAY was created.
+ for (WindowManagerState.WindowState win : alertWindows) {
+ assertTrue("Can't create win=" + win + " on SDK O or greater",
+ win.getType() == TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ final WindowManagerState.WindowState mainAppWindow =
+ wMState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
+
+ assertNotNull(mainAppWindow);
+
+ final WindowManagerState.WindowState lowestAlertWindow = alertWindows.get(0);
+ final WindowManagerState.WindowState highestAlertWindow =
+ alertWindows.get(alertWindows.size() - 1);
+
+ // Assert that the alert windows have higher z-order than the main app window
+ final WindowManagerState wmState = mAmWmState.getWmState();
+ assertTrue("lowestAlertWindow=" + lowestAlertWindow + " less than mainAppWindow="
+ + mainAppWindow,
+ wmState.getZOrder(lowestAlertWindow) > wmState.getZOrder(mainAppWindow));
+
+ // Assert that legacy alert windows have a lower z-order than the new alert window layer.
+ final WindowManagerState.WindowState appOverlayWindow =
+ wMState.getWindowByPackageName(packageName, TYPE_APPLICATION_OVERLAY);
+ if (appOverlayWindow != null && highestAlertWindow != appOverlayWindow) {
+ assertTrue("highestAlertWindow=" + highestAlertWindow
+ + " greater than appOverlayWindow=" + appOverlayWindow,
+ wmState.getZOrder(highestAlertWindow) < wmState.getZOrder(appOverlayWindow));
+ }
+
+ // Assert that alert windows are below key system windows.
+ final ArrayList<WindowManagerState.WindowState> systemWindows = new ArrayList<>();
+ wMState.getWindowsByPackageName(packageName, mSystemWindowTypes, systemWindows);
+ if (!systemWindows.isEmpty()) {
+ final WindowManagerState.WindowState lowestSystemWindow = alertWindows.get(0);
+ assertTrue("highestAlertWindow=" + highestAlertWindow
+ + " greater than lowestSystemWindow=" + lowestSystemWindow,
+ wmState.getZOrder(highestAlertWindow) < wmState.getZOrder(lowestSystemWindow));
+ }
+ }
+
+ private void setAlertWindowPermission(final ComponentName activityName, final boolean allow) {
+ final String packageName = activityName.getPackageName();
+ executeShellCommand("appops set " + packageName + " android:system_alert_window "
+ + (allow ? "allow" : "deny"));
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ChildMovementTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ChildMovementTests.java
new file mode 100644
index 0000000..41f620d
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ChildMovementTests.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.server.am.SurfaceTraceReceiver;
+import android.server.am.WaitForValidActivityState;
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChildMovementTests extends ParentChildTestBase {
+
+ private static final ComponentName MOVING_CHILD_TEST_ACTIVITY = ComponentName
+ .unflattenFromString("android.server.wm.frametestapp/.MovingChildTestActivity");
+
+ /** @see android.server.wm.frametestapp.MovingChildTestActivity#POPUP_WINDOW_NAME */
+ private static final String POPUP_WINDOW_NAME = "ChildWindow";
+
+ private List<WindowState> mWindowList = new ArrayList<>();
+
+ @Override
+ ComponentName activityName() {
+ return MOVING_CHILD_TEST_ACTIVITY;
+ }
+
+ private WindowState getSingleWindow(final String windowName) {
+ try {
+ mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mWindowList);
+ return mWindowList.get(0);
+ } catch (Exception e) {
+ logE("Couldn't find window: " + windowName);
+ return null;
+ }
+ }
+
+ @Override
+ void doSingleTest(ParentChildTest t) throws Exception {
+ final WaitForValidActivityState waitForVisible =
+ WaitForValidActivityState.forWindow(POPUP_WINDOW_NAME);
+
+ mAmWmState.computeState(waitForVisible);
+ WindowState popup = getSingleWindow(POPUP_WINDOW_NAME);
+ WindowState parent = getSingleWindow(activityName().flattenToString());
+
+ t.doTest(parent, popup);
+ }
+
+ private final Object monitor = new Object();
+ private boolean testPassed = false;
+ private String popupName = null;
+ private String mainName = null;
+
+ private final SurfaceTraceReceiver.SurfaceObserver observer =
+ new SurfaceTraceReceiver.SurfaceObserver() {
+ int transactionCount = 0;
+ boolean sawChildMove = false;
+ boolean sawMainMove = false;
+ int timesSeen = 0;
+
+ @Override
+ public void openTransaction() {
+ transactionCount++;
+ if (transactionCount == 1) {
+ sawChildMove = false;
+ sawMainMove = false;
+ }
+ }
+
+ @Override
+ public void closeTransaction() {
+ transactionCount--;
+ if (transactionCount != 0) {
+ return;
+ }
+ synchronized (monitor) {
+ if (sawChildMove ^ sawMainMove) {
+ monitor.notifyAll();
+ return;
+ }
+ if (timesSeen > 10) {
+ testPassed = true;
+ monitor.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void setPosition(String windowName, float x, float y) {
+ if (windowName.equals(popupName)) {
+ sawChildMove = true;
+ timesSeen++;
+ } else if (windowName.equals(mainName)) {
+ sawMainMove = true;
+ }
+ }
+ };
+
+ /**
+ * Here we test that a Child moves in the same transaction
+ * as its parent. We launch an activity with a Child which will
+ * move around its own main window. Then we listen to WindowManager transactions.
+ * Since the Child is static within the window, if we ever see one of
+ * them move xor the other one we have a problem!
+ */
+ @Test
+ public void testSurfaceMovesWithParent() throws Exception {
+ doFullscreenTest("MovesWithParent",
+ (WindowState parent, WindowState popup) -> {
+ popupName = popup.getName();
+ mainName = parent.getName();
+ installSurfaceObserver(observer);
+ try {
+ synchronized (monitor) {
+ monitor.wait(5000);
+ }
+ } catch (InterruptedException e) {
+ } finally {
+ assertTrue(testPassed);
+ removeSurfaceObserver();
+ }
+ });
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
new file mode 100644
index 0000000..c9b8b17
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.server.am.ActivityManagerTestBase.executeShellCommand;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.CrossAppDragAndDropTests
+ */
+//@Presubmit b/68038788
+public class CrossAppDragAndDropTests {
+ private static final String TAG = "CrossAppDragAndDrop";
+
+ private static final String AM_FORCE_STOP = "am force-stop ";
+ private static final String AM_MOVE_TASK = "am stack move-task ";
+ private static final String AM_RESIZE_TASK = "am task resize ";
+ private static final String AM_REMOVE_STACK = "am stack remove ";
+ private static final String AM_START_N = "am start -n ";
+ private static final String AM_STACK_LIST = "am stack list";
+ private static final String TASK_ID_PREFIX = "taskId";
+
+ // Regex pattern to match adb shell am stack list output of the form:
+ // taskId=<TASK_ID>: <componentName> bounds=[LEFT,TOP][RIGHT,BOTTOM]
+ private static final String TASK_REGEX_PATTERN_STRING =
+ "taskId=[0-9]+: %s bounds=\\[[0-9]+,[0-9]+\\]\\[[0-9]+,[0-9]+\\]";
+
+ private static final int SWIPE_STEPS = 100;
+
+ private static final String SOURCE_PACKAGE_NAME = "android.server.wm.dndsourceapp";
+ private static final String TARGET_PACKAGE_NAME = "android.server.wm.dndtargetapp";
+ private static final String TARGET_23_PACKAGE_NAME = "android.server.wm.dndtargetappsdk23";
+
+
+ private static final String SOURCE_ACTIVITY_NAME = "DragSource";
+ private static final String TARGET_ACTIVITY_NAME = "DropTarget";
+
+ private static final String FILE_GLOBAL = "file_global";
+ private static final String FILE_LOCAL = "file_local";
+ private static final String DISALLOW_GLOBAL = "disallow_global";
+ private static final String CANCEL_SOON = "cancel_soon";
+ private static final String GRANT_NONE = "grant_none";
+ private static final String GRANT_READ = "grant_read";
+ private static final String GRANT_WRITE = "grant_write";
+ private static final String GRANT_READ_PREFIX = "grant_read_prefix";
+ private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
+ private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
+
+ private static final String REQUEST_NONE = "request_none";
+ private static final String REQUEST_READ = "request_read";
+ private static final String REQUEST_READ_NESTED = "request_read_nested";
+ private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
+ private static final String REQUEST_WRITE = "request_write";
+
+ private static final String SOURCE_LOG_TAG = "DragSource";
+ private static final String TARGET_LOG_TAG = "DropTarget";
+
+ private static final String RESULT_KEY_START_DRAG = "START_DRAG";
+ private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+ private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
+ private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+ private static final String RESULT_KEY_DROP_RESULT = "DROP";
+ private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
+ private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
+ private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
+ private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
+ private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
+
+ private static final String RESULT_MISSING = "Missing";
+ private static final String RESULT_OK = "OK";
+ private static final String RESULT_EXCEPTION = "Exception";
+ private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
+
+ protected Context mContext;
+ protected ActivityManager mAm;
+ private UiDevice mDevice;
+
+ private Map<String, String> mSourceResults;
+ private Map<String, String> mTargetResults;
+
+ private String mSourcePackageName;
+ private String mTargetPackageName;
+
+ private String mSessionId;
+ private String mSourceLogTag;
+ private String mTargetLogTag;
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(supportsDragAndDrop());
+
+ // Use uptime in seconds as unique test invocation id.
+ mSessionId = Long.toString(SystemClock.uptimeMillis() / 1000);
+ mSourceLogTag = SOURCE_LOG_TAG + mSessionId;
+ mTargetLogTag = TARGET_LOG_TAG + mSessionId;
+
+ mContext = InstrumentationRegistry.getContext();
+ mAm = mContext.getSystemService(ActivityManager.class);
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ mSourcePackageName = SOURCE_PACKAGE_NAME;
+ mTargetPackageName = TARGET_PACKAGE_NAME;
+ cleanupState();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assumeTrue(supportsDragAndDrop());
+
+ executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
+ executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
+ }
+
+ private void clearLogs() {
+ executeShellCommand("logcat -c");
+ }
+
+ private String getStartCommand(String componentName, String modeExtra, String logtag) {
+ return AM_START_N + componentName + " -e mode " + modeExtra + " -e logtag " + logtag;
+ }
+
+ private String getMoveTaskCommand(int taskId, int stackId) {
+ return AM_MOVE_TASK + taskId + " " + stackId + " true";
+ }
+
+ private String getResizeTaskCommand(int taskId, Point topLeft, Point bottomRight)
+ throws Exception {
+ return AM_RESIZE_TASK + taskId + " " + topLeft.x + " " + topLeft.y + " " + bottomRight.x
+ + " " + bottomRight.y;
+ }
+
+ private String getComponentName(String packageName, String activityName) {
+ return packageName + "/" + packageName + "." + activityName;
+ }
+
+ /**
+ * Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
+ * is in a good state.
+ */
+ private void cleanupState() throws Exception {
+ executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
+ executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
+ executeShellCommand(AM_FORCE_STOP + TARGET_23_PACKAGE_NAME);
+ unlockDevice();
+ clearLogs();
+
+ // Remove special stacks.
+ mAm.removeStacksInWindowingModes(new int[] {
+ WINDOWING_MODE_PINNED,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+ WINDOWING_MODE_FREEFORM
+ });
+ }
+
+ private void launchDockedActivity(String packageName, String activityName, String mode,
+ String logtag) throws Exception {
+ clearLogs();
+ final String componentName = getComponentName(packageName, activityName);
+ executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
+ + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ waitForResume(packageName, activityName);
+ }
+
+ private void launchFullscreenActivity(String packageName, String activityName, String mode,
+ String logtag) throws Exception {
+ clearLogs();
+ final String componentName = getComponentName(packageName, activityName);
+ executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
+ + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+ waitForResume(packageName, activityName);
+ }
+
+ /**
+ * @param displaySize size of the display
+ * @param leftSide {@code true} to launch the app taking up the left half of the display,
+ * {@code false} to launch the app taking up the right half of the display.
+ */
+ private void launchFreeformActivity(String packageName, String activityName, String mode,
+ String logtag, Point displaySize, boolean leftSide) throws Exception {
+ clearLogs();
+ final String componentName = getComponentName(packageName, activityName);
+ executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
+ + WINDOWING_MODE_FREEFORM);
+ waitForResume(packageName, activityName);
+ Point topLeft = new Point(leftSide ? 0 : displaySize.x / 2, 0);
+ Point bottomRight = new Point(leftSide ? displaySize.x / 2 : displaySize.x, displaySize.y);
+ executeShellCommand(getResizeTaskCommand(getActivityTaskId(componentName), topLeft,
+ bottomRight));
+ }
+
+ private void waitForResume(String packageName, String activityName) throws Exception {
+ final String fullActivityName = packageName + "." + activityName;
+ int retryCount = 3;
+ do {
+ Thread.sleep(500);
+ String logs = executeShellCommand("logcat -d -b events");
+ for (String line : logs.split("\\n")) {
+ if (line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
+ return;
+ }
+ }
+ } while (retryCount-- > 0);
+
+ throw new Exception(fullActivityName + " has failed to start");
+ }
+
+ private void injectInput(Point from, Point to, int steps) throws Exception {
+ mDevice.drag(from.x, from.y, to.x, to.y, steps);
+ }
+
+ private String findTaskInfo(String name) {
+ final String output = executeShellCommand(AM_STACK_LIST);
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Finding task info for task: ");
+ builder.append(name);
+ builder.append("\nParsing adb shell am output: ");
+ builder.append(output);
+ log(builder.toString());
+ final Pattern pattern = Pattern.compile(String.format(TASK_REGEX_PATTERN_STRING, name));
+ for (String line : output.split("\\n")) {
+ final String truncatedLine;
+ // Only look for the activity name before the "topActivity" string.
+ final int pos = line.indexOf("topActivity");
+ if (pos > 0) {
+ truncatedLine = line.substring(0, pos);
+ } else {
+ truncatedLine = line;
+ }
+ if (pattern.matcher(truncatedLine).find()) {
+ return truncatedLine;
+ }
+ }
+ return "";
+ }
+
+ private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
+ final String taskInfo = findTaskInfo(name);
+ final String[] sections = taskInfo.split("\\[");
+ if (sections.length > 2) {
+ try {
+ parsePoint(sections[1], from);
+ parsePoint(sections[2], to);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private int getActivityTaskId(String name) {
+ final String taskInfo = findTaskInfo(name);
+ for (String word : taskInfo.split("\\s+")) {
+ if (word.startsWith(TASK_ID_PREFIX)) {
+ final String withColon = word.split("=")[1];
+ return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
+ }
+ }
+ return -1;
+ }
+
+ private Point getDisplaySize() throws Exception {
+ final String output = executeShellCommand("wm size");
+ final String[] sizes = output.split(" ")[2].split("x");
+ return new Point(Integer.valueOf(sizes[0].trim()), Integer.valueOf(sizes[1].trim()));
+ }
+
+ private Point getWindowCenter(String name) throws Exception {
+ Point p1 = new Point();
+ Point p2 = new Point();
+ if (getWindowBounds(name, p1, p2)) {
+ return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
+ }
+ return null;
+ }
+
+ private void parsePoint(String string, Point point) {
+ final String[] parts = string.split("[,|\\]]");
+ point.x = Integer.parseInt(parts[0]);
+ point.y = Integer.parseInt(parts[1]);
+ }
+
+ private void unlockDevice() {
+ // Wake up the device, if necessary.
+ try {
+ mDevice.wakeUp();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ // Unlock the screen.
+ mDevice.pressMenu();
+ }
+
+ private void assertDropResult(String sourceMode, String targetMode, String expectedDropResult)
+ throws Exception {
+ assertDragAndDropResults(sourceMode, targetMode, RESULT_OK, expectedDropResult, RESULT_OK);
+ }
+
+ private void assertNoGlobalDragEvents(String sourceMode, String expectedStartDragResult)
+ throws Exception {
+ assertDragAndDropResults(
+ sourceMode, REQUEST_NONE, expectedStartDragResult, RESULT_MISSING, RESULT_MISSING);
+ }
+
+ private void assertDragAndDropResults(String sourceMode, String targetMode,
+ String expectedStartDragResult, String expectedDropResult,
+ String expectedListenerResults) throws Exception {
+ Log.e(TAG, "session: " + mSessionId + ", source: " + sourceMode
+ + ", target: " + targetMode);
+
+ if (supportsSplitScreenMultiWindow()) {
+ launchDockedActivity(
+ mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode, mSourceLogTag);
+ launchFullscreenActivity(
+ mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode, mTargetLogTag);
+ } else if (supportsFreeformMultiWindow()) {
+ // Fallback to try to launch two freeform windows side by side.
+ Point displaySize = getDisplaySize();
+ launchFreeformActivity(
+ mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode, mSourceLogTag,
+ displaySize, true /* leftSide */);
+ launchFreeformActivity(
+ mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode, mTargetLogTag,
+ displaySize, false /* leftSide */);
+ } else {
+ return;
+ }
+
+ Point p1 = getWindowCenter(getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME));
+ assertNotNull(p1);
+ Point p2 = getWindowCenter(getComponentName(mTargetPackageName, TARGET_ACTIVITY_NAME));
+ assertNotNull(p2);
+
+ TestLogService.registerClient(mSourceLogTag, RESULT_KEY_START_DRAG);
+ TestLogService.registerClient(mTargetLogTag, RESULT_KEY_DRAG_ENDED);
+
+ injectInput(p1, p2, SWIPE_STEPS);
+
+ mSourceResults = TestLogService.getResultsForClient(mSourceLogTag, 1000);
+ assertSourceResult(RESULT_KEY_START_DRAG, expectedStartDragResult);
+
+ mTargetResults = TestLogService.getResultsForClient(mTargetLogTag, 1000);
+ assertTargetResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
+ if (!RESULT_MISSING.equals(expectedDropResult)) {
+ assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
+ assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
+ }
+ assertListenerResults(expectedListenerResults);
+ }
+
+ private void assertListenerResults(String expectedResult) throws Exception {
+ assertTargetResult(RESULT_KEY_DRAG_STARTED, expectedResult);
+ assertTargetResult(RESULT_KEY_DRAG_ENDED, expectedResult);
+ assertTargetResult(RESULT_KEY_EXTRAS, expectedResult);
+
+ assertTargetResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
+ assertTargetResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING);
+ assertTargetResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_MISSING);
+ }
+
+ private void assertSourceResult(String resultKey, String expectedResult) throws Exception {
+ assertResult(mSourceResults, resultKey, expectedResult);
+ }
+
+ private void assertTargetResult(String resultKey, String expectedResult) throws Exception {
+ assertResult(mTargetResults, resultKey, expectedResult);
+ }
+
+ private void assertResult(Map<String, String> results, String resultKey, String expectedResult)
+ throws Exception {
+ if (RESULT_MISSING.equals(expectedResult)) {
+ if (results.containsKey(resultKey)) {
+ fail("Unexpected " + resultKey + "=" + results.get(resultKey));
+ }
+ } else {
+ assertTrue("Missing " + resultKey, results.containsKey(resultKey));
+ assertEquals(resultKey + " result mismatch,", expectedResult,
+ results.get(resultKey));
+ }
+ }
+
+ private static boolean supportsDragAndDrop() {
+ return ActivityManager.supportsMultiWindow(InstrumentationRegistry.getContext());
+ }
+
+ private static boolean supportsSplitScreenMultiWindow() {
+ return ActivityManager.supportsSplitScreenMultiWindow(InstrumentationRegistry.getContext());
+ }
+
+ private static boolean supportsFreeformMultiWindow() {
+ return InstrumentationRegistry.getContext()
+ .getPackageManager()
+ .hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ }
+
+ @Test
+ public void testCancelSoon() throws Exception {
+ assertDropResult(CANCEL_SOON, REQUEST_NONE, RESULT_MISSING);
+ }
+
+ @Test
+ public void testDisallowGlobal() throws Exception {
+ assertNoGlobalDragEvents(DISALLOW_GLOBAL, RESULT_OK);
+ }
+
+ @Test
+ public void testDisallowGlobalBelowSdk24() throws Exception {
+ mTargetPackageName = TARGET_23_PACKAGE_NAME;
+ assertNoGlobalDragEvents(GRANT_NONE, RESULT_OK);
+ }
+
+ @Test
+ public void testFileUriLocal() throws Exception {
+ assertNoGlobalDragEvents(FILE_LOCAL, RESULT_OK);
+ }
+
+ @Test
+ public void testFileUriGlobal() throws Exception {
+ assertNoGlobalDragEvents(FILE_GLOBAL, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantNoneRequestNone() throws Exception {
+ assertDropResult(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantNoneRequestRead() throws Exception {
+ assertDropResult(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
+ }
+
+ @Test
+ public void testGrantNoneRequestWrite() throws Exception {
+ assertDropResult(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
+ }
+
+ @Test
+ public void testGrantReadRequestNone() throws Exception {
+ assertDropResult(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantReadRequestRead() throws Exception {
+ assertDropResult(GRANT_READ, REQUEST_READ, RESULT_OK);
+ }
+
+ @Test
+ public void testGrantReadRequestWrite() throws Exception {
+ assertDropResult(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantReadNoPrefixRequestReadNested() throws Exception {
+ assertDropResult(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantReadPrefixRequestReadNested() throws Exception {
+ assertDropResult(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
+ }
+
+ @Test
+ public void testGrantPersistableRequestTakePersistable() throws Exception {
+ assertDropResult(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
+ }
+
+ @Test
+ public void testGrantReadRequestTakePersistable() throws Exception {
+ assertDropResult(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantWriteRequestNone() throws Exception {
+ assertDropResult(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantWriteRequestRead() throws Exception {
+ assertDropResult(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testGrantWriteRequestWrite() throws Exception {
+ assertDropResult(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
new file mode 100644
index 0000000..a5e5b4b
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.server.am.WaitForValidActivityState;
+import android.server.am.WindowManagerState;
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DialogFrameTests extends ParentChildTestBase {
+
+ private static final ComponentName DIALOG_TEST_ACTIVITY = ComponentName
+ .unflattenFromString("android.server.wm.frametestapp/.DialogTestActivity");
+
+ /** @see android.server.wm.frametestapp.DialogTestActivity#DIALOG_WINDOW_NAME */
+ private static final String DIALOG_WINDOW_NAME = "TestDialog";
+
+ private List<WindowState> mWindowList = new ArrayList<>();
+
+ @Override
+ ComponentName activityName() {
+ return DIALOG_TEST_ACTIVITY;
+ }
+
+ private WindowState getSingleWindow(final String windowName) {
+ try {
+ mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mWindowList);
+ return mWindowList.get(0);
+ } catch (Exception e) {
+ logE("Couldn't find window: " + windowName);
+ return null;
+ }
+ }
+
+ @Override
+ void doSingleTest(ParentChildTest t) throws Exception {
+ final WaitForValidActivityState waitForVisible =
+ WaitForValidActivityState.forWindow(DIALOG_WINDOW_NAME);
+
+ mAmWmState.computeState(waitForVisible);
+ WindowState dialog = getSingleWindow(DIALOG_WINDOW_NAME);
+ WindowState parent = getSingleWindow(activityName().flattenToString());
+
+ t.doTest(parent, dialog);
+ }
+
+ // With Width and Height as MATCH_PARENT we should fill
+ // the same content frame as the main activity window
+ @Test
+ public void testMatchParentDialog() throws Exception {
+ doParentChildTest("MatchParent",
+ (WindowState parent, WindowState dialog) -> {
+ assertEquals(parent.getContentFrame(), dialog.getFrame());
+ });
+ }
+
+ // If we have LAYOUT_IN_SCREEN and LAYOUT_IN_OVERSCAN with MATCH_PARENT,
+ // we will not be constrained to the insets and so we will be the same size
+ // as the main window main frame.
+ @Test
+ public void testMatchParentDialogLayoutInOverscan() throws Exception {
+ doParentChildTest("MatchParentLayoutInOverscan",
+ (WindowState parent, WindowState dialog) -> {
+ assertEquals(parent.getFrame(), dialog.getFrame());
+ });
+ }
+
+ private static final int explicitDimension = 200;
+
+ // The default gravity for dialogs should center them.
+ @Test
+ public void testExplicitSizeDefaultGravity() throws Exception {
+ doParentChildTest("ExplicitSize",
+ (WindowState parent, WindowState dialog) -> {
+ Rect contentFrame = parent.getContentFrame();
+ Rect expectedFrame = new Rect(
+ contentFrame.left + (contentFrame.width() - explicitDimension) / 2,
+ contentFrame.top + (contentFrame.height() - explicitDimension) / 2,
+ contentFrame.left + (contentFrame.width() + explicitDimension) / 2,
+ contentFrame.top + (contentFrame.height() + explicitDimension) / 2);
+ assertEquals(expectedFrame, dialog.getFrame());
+ });
+ }
+
+ @Test
+ public void testExplicitSizeTopLeftGravity() throws Exception {
+ doParentChildTest("ExplicitSizeTopLeftGravity",
+ (WindowState parent, WindowState dialog) -> {
+ Rect contentFrame = parent.getContentFrame();
+ Rect expectedFrame = new Rect(
+ contentFrame.left,
+ contentFrame.top,
+ contentFrame.left + explicitDimension,
+ contentFrame.top + explicitDimension);
+ assertEquals(expectedFrame, dialog.getFrame());
+ });
+ }
+
+ @Test
+ public void testExplicitSizeBottomRightGravity() throws Exception {
+ doParentChildTest("ExplicitSizeBottomRightGravity",
+ (WindowState parent, WindowState dialog) -> {
+ Rect contentFrame = parent.getContentFrame();
+ Rect expectedFrame = new Rect(
+ contentFrame.left + contentFrame.width() - explicitDimension,
+ contentFrame.top + contentFrame.height() - explicitDimension,
+ contentFrame.left + contentFrame.width(),
+ contentFrame.top + contentFrame.height());
+ assertEquals(expectedFrame, dialog.getFrame());
+ });
+ }
+
+ // TODO: Commented out for now because it doesn't work. We end up
+ // insetting the decor on the bottom. I think this is a bug
+ // probably in the default dialog flags:
+ // b/30127373
+ // public void testOversizedDimensions() throws Exception {
+ // doParentChildTest("OversizedDimensions",
+ // (WindowState parent, WindowState dialog) -> {
+ // With the default flags oversize should result in clipping to
+ // parent frame.
+ // assertEquals(parent.getContentFrame(), dialog.getFrame());
+ // });
+ // }
+
+
+
+ // TODO(b/63993863) : Disabled pending public API to fetch maximum surface size.
+ //static final int oversizedDimension = 5000;
+ // With FLAG_LAYOUT_NO_LIMITS we should get the size we request, even if its much
+ // larger than the screen.
+ // @Test
+ // public void testOversizedDimensionsNoLimits() throws Exception {
+ // TODO(b/36890978): We only run this in fullscreen because of the
+ // unclear status of NO_LIMITS for non-child surfaces in MW modes
+ // doFullscreenTest("OversizedDimensionsNoLimits",
+ // (WindowState parent, WindowState dialog) -> {
+ // Rect contentFrame = parent.getContentFrame();
+ // Rect expectedFrame = new Rect(contentFrame.left, contentFrame.top,
+ // contentFrame.left + oversizedDimension,
+ // contentFrame.top + oversizedDimension);
+ // assertEquals(expectedFrame, dialog.getFrame());
+ // });
+ //}
+
+ // If we request the MATCH_PARENT and a non-zero position, we wouldn't be
+ // able to fit all of our content, so we should be adjusted to just fit the
+ // content frame.
+ @Test
+ public void testExplicitPositionMatchParent() throws Exception {
+ doParentChildTest("ExplicitPositionMatchParent",
+ (WindowState parent, WindowState dialog) -> {
+ assertEquals(parent.getContentFrame(),
+ dialog.getFrame());
+ });
+ }
+
+ // Unless we pass NO_LIMITS in which case our requested position should
+ // be honored.
+ @Test
+ public void testExplicitPositionMatchParentNoLimits() throws Exception {
+ final int explicitPosition = 100;
+ doParentChildTest("ExplicitPositionMatchParentNoLimits",
+ (WindowState parent, WindowState dialog) -> {
+ Rect contentFrame = parent.getContentFrame();
+ Rect expectedFrame = new Rect(contentFrame);
+ expectedFrame.offset(explicitPosition, explicitPosition);
+ assertEquals(expectedFrame, dialog.getFrame());
+ });
+ }
+
+ // We run the two focus tests fullscreen only because switching to the
+ // docked stack will strip away focus from the task anyway.
+ @Test
+ public void testDialogReceivesFocus() throws Exception {
+ doFullscreenTest("MatchParent",
+ (WindowState parent, WindowState dialog) -> {
+ assertEquals(dialog.getName(), mAmWmState.getWmState().getFocusedWindow());
+ });
+ }
+
+ @Test
+ public void testNoFocusDialog() throws Exception {
+ doFullscreenTest("NoFocus",
+ (WindowState parent, WindowState dialog) -> {
+ assertEquals(parent.getName(), mAmWmState.getWmState().getFocusedWindow());
+ });
+ }
+
+ @Test
+ public void testMarginsArePercentagesOfContentFrame() throws Exception {
+ float horizontalMargin = .25f;
+ float verticalMargin = .35f;
+ doParentChildTest("WithMargins",
+ (WindowState parent, WindowState dialog) -> {
+ Rect frame = parent.getContentFrame();
+ Rect expectedFrame = new Rect(
+ (int) (horizontalMargin * frame.width() + frame.left),
+ (int) (verticalMargin * frame.height() + frame.top),
+ (int) (horizontalMargin * frame.width() + frame.left)
+ + explicitDimension,
+ (int) (verticalMargin * frame.height() + frame.top)
+ + explicitDimension);
+ assertEquals(expectedFrame, dialog.getFrame());
+ });
+ }
+
+ @Test
+ public void testDialogPlacedAboveParent() throws Exception {
+ final WindowManagerState wmState = mAmWmState.getWmState();
+ doParentChildTest("MatchParent",
+ (WindowState parent, WindowState dialog) -> {
+ // Not only should the dialog be higher, but it should be
+ // leave multiple layers of space inbetween for DimLayers,
+ // etc...
+ assertTrue(wmState.getZOrder(dialog) > wmState.getZOrder(parent));
+ });
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
new file mode 100644
index 0000000..08f25c2
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.StateLogger.log;
+
+import android.content.ComponentName;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.WindowManagerState.WindowState;
+
+abstract class ParentChildTestBase extends ActivityManagerTestBase {
+
+ /** Extra key for test case name. */
+ private static final String EXTRA_TEST_CASE = "test-case";
+
+ interface ParentChildTest {
+ void doTest(WindowState parent, WindowState child);
+ }
+
+ private void startTestCase(String testCase) throws Exception {
+ executeShellCommand(getAmStartCmd(activityName(), EXTRA_TEST_CASE, testCase));
+ }
+
+ private void startTestCaseDocked(String testCase) throws Exception {
+ startTestCase(testCase);
+ setActivityTaskWindowingMode(activityName(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ }
+
+ abstract ComponentName activityName();
+
+ abstract void doSingleTest(ParentChildTest t) throws Exception;
+
+ void doFullscreenTest(String testCase, ParentChildTest t) throws Exception {
+ log("Running test fullscreen");
+ startTestCase(testCase);
+ doSingleTest(t);
+ stopTestPackage(activityName());
+ }
+
+ private void doDockedTest(String testCase, ParentChildTest t) throws Exception {
+ log("Running test docked");
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+ startTestCaseDocked(testCase);
+ doSingleTest(t);
+ stopTestPackage(activityName());
+ }
+
+ void doParentChildTest(String testCase, ParentChildTest t) throws Exception {
+ doFullscreenTest(testCase, t);
+ doDockedTest(testCase, t);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TestLogClient.java b/tests/framework/base/windowmanager/src/android/server/wm/TestLogClient.java
new file mode 100644
index 0000000..725ef09
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TestLogClient.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * A class that sends log data to {@link TestLogService}.
+ */
+public class TestLogClient {
+
+ public static final String EXTRA_LOG_TAG = "logtag";
+ public static final String EXTRA_KEY = "key";
+ public static final String EXTRA_VALUE = "value";
+
+ private static final String TEST_LOGGER_PACKAGE_NAME = "android.server.cts.wm";
+ private static final String TEST_LOGGER_SERVICE_NAME = "android.server.wm.TestLogService";
+
+ private final Context mContext;
+ private final String mLogTag;
+
+ public TestLogClient(Context context, String logtag) {
+ mContext = context;
+ mLogTag = logtag;
+ }
+
+ public void record(String key, String value) {
+ Intent intent = new Intent();
+ intent.setComponent(
+ new ComponentName(TEST_LOGGER_PACKAGE_NAME, TEST_LOGGER_SERVICE_NAME));
+ intent.putExtra(EXTRA_LOG_TAG, mLogTag);
+ intent.putExtra(EXTRA_KEY, key);
+ intent.putExtra(EXTRA_VALUE, value);
+ mContext.startService(intent);
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TestLogService.java b/tests/framework/base/windowmanager/src/android/server/wm/TestLogService.java
new file mode 100644
index 0000000..8bd5099
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TestLogService.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A service collecting data from other apps used by a test.
+ *
+ * Use {@link TestLogClient} to send data to this service.
+ */
+public class TestLogService extends Service {
+ private static final String TAG = "TestLogService";
+
+ private static final Object mLock = new Object();
+
+ static class ClientChannel {
+ final String mStopKey;
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ final Map<String, String> mResults = new HashMap<>();
+
+ ClientChannel(String stopKey) {
+ mStopKey = stopKey;
+ }
+ }
+
+ private static Map<String, ClientChannel> mChannels = new HashMap<>();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ record(intent.getStringExtra(TestLogClient.EXTRA_LOG_TAG),
+ intent.getStringExtra(TestLogClient.EXTRA_KEY),
+ intent.getStringExtra(TestLogClient.EXTRA_VALUE));
+ return START_NOT_STICKY;
+ }
+
+ /**
+ * Prepare to receive results from a client with a specified tag.
+ *
+ * @param logtag Unique tag for the client.
+ * @param stopKey The key that signals that the client has completed all required actions.
+ */
+ public static void registerClient(String logtag, String stopKey) {
+ synchronized (mLock) {
+ if (mChannels.containsKey(logtag)) {
+ throw new IllegalArgumentException(logtag);
+ }
+ mChannels.put(logtag, new ClientChannel(stopKey));
+ }
+ }
+
+ /**
+ * Wait for the client to complete all required actions and return the results.
+ *
+ * @param logtag Unique tag for the client.
+ * @param timeoutMs Latch timeout in ms.
+ * @return The map of results from the client
+ */
+ public static Map<String, String> getResultsForClient(String logtag, int timeoutMs) {
+ Map<String, String> result = new HashMap<>();
+ CountDownLatch latch;
+ synchronized (mLock) {
+ if (!mChannels.containsKey(logtag)) {
+ return result;
+ }
+ latch = mChannels.get(logtag).mLatch;
+ }
+ try {
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignore) {
+ }
+ synchronized (mLock) {
+ for (Map.Entry<String, String> e : mChannels.get(logtag).mResults.entrySet()) {
+ result.put(e.getKey(), e.getValue());
+ }
+ }
+ return result;
+ }
+
+ private static void record(String logtag, String key, String value) {
+ synchronized (mLock) {
+ if (!mChannels.containsKey(logtag)) {
+ Log.e(TAG, "Unexpected logtag: " + logtag);
+ return;
+ }
+ ClientChannel channel = mChannels.get(logtag);
+ channel.mResults.put(key, value);
+ if (key.equals(channel.mStopKey)) {
+ channel.mLatch.countDown();
+ }
+ }
+ }
+}
diff --git a/tests/inputmethod/Android.mk b/tests/inputmethod/Android.mk
index 0c1ed5f..5300762 100644
--- a/tests/inputmethod/Android.mk
+++ b/tests/inputmethod/Android.mk
@@ -31,12 +31,19 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
compatibility-device-util \
- ctstestrunner
+ ctstestrunner \
+ CtsMockInputMethod
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src)
+
+LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/src
LOCAL_PACKAGE_NAME := CtsInputMethodTestCases
+LOCAL_SDK_VERSION := test_current
+
include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 11f008d..a5bc532 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -34,6 +34,50 @@
</intent-filter>
</activity>
+ <activity
+ android:name="android.view.inputmethod.cts.util.TestActivity"
+ android:label="TestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="android.view.inputmethod.cts.util.StateInitializeActivity"
+ android:label="StateInitializeActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <!-- In order to test typical use cases, let this MockIME run in a separate process -->
+ <!--
+ TODO: Move this sevice definition into MockIME package and let the build system to merge
+ manifests once soong supports to build aar package.
+ -->
+ <service
+ android:name="com.android.cts.mockime.MockIme"
+ android:label="Mock IME"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:process=":mockime">
+ <intent-filter>
+ <action android:name="android.view.InputMethod" />
+ </intent-filter>
+ <meta-data
+ android:name="android.view.im"
+ android:resource="@xml/method" />
+ </service>
+
+ <!--
+ In order to test window-focus-stealing from other process, let this service run in a
+ separate process. -->
+ <service android:name="android.view.inputmethod.cts.util.WindowFocusStealerService"
+ android:process=":focusstealer"
+ android:exported="false">
+ </service>
+
</application>
<instrumentation
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index b696a52..2cb0099 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -16,9 +16,16 @@
-->
<configuration description="Config for CTS InputMethod test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <!--
+ TODO(yukawa): come up with a proper way to take care of devices that do not support
+ installable IMEs. Ideally target_preparer should have an option to annotate required
+ features, e.g. android.software.input_methods so that we can conditionally install APKs
+ based on the feature supported in the target device.
+ -->
<option name="test-file-name" value="CtsInputMethodTestCases.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/inputmethod/mockime/Android.mk b/tests/inputmethod/mockime/Android.mk
new file mode 100644
index 0000000..10f80a6
--- /dev/null
+++ b/tests/inputmethod/mockime/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsMockInputMethod
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-annotations \
+ compatibility-device-util
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
new file mode 100644
index 0000000..48d4e05
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeCommand.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+public final class ImeCommand {
+
+ private static final String NAME_KEY = "name";
+ private static final String ID_KEY = "id";
+ private static final String DISPATCH_TO_MAIN_THREAD_KEY = "dispatchToMainThread";
+ private static final String EXTRA_KEY = "extra";
+
+ @NonNull
+ private final String mName;
+ private final long mId;
+ private final boolean mDispatchToMainThread;
+ @NonNull
+ private final Bundle mExtras;
+
+ ImeCommand(@NonNull String name, long id, boolean dispatchToMainThread,
+ @NonNull Bundle extras) {
+ mName = name;
+ mId = id;
+ mDispatchToMainThread = dispatchToMainThread;
+ mExtras = extras;
+ }
+
+ private ImeCommand(@NonNull Bundle bundle) {
+ mName = bundle.getString(NAME_KEY);
+ mId = bundle.getLong(ID_KEY);
+ mDispatchToMainThread = bundle.getBoolean(DISPATCH_TO_MAIN_THREAD_KEY);
+ mExtras = bundle.getParcelable(EXTRA_KEY);
+ }
+
+ static ImeCommand fromBundle(@NonNull Bundle bundle) {
+ return new ImeCommand(bundle);
+ }
+
+ Bundle toBundle() {
+ final Bundle bundle = new Bundle();
+ bundle.putString(NAME_KEY, mName);
+ bundle.putLong(ID_KEY, mId);
+ bundle.putBoolean(DISPATCH_TO_MAIN_THREAD_KEY, mDispatchToMainThread);
+ bundle.putParcelable(EXTRA_KEY, mExtras);
+ return bundle;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public boolean shouldDispatchToMainThread() {
+ return mDispatchToMainThread;
+ }
+
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
\ No newline at end of file
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
new file mode 100644
index 0000000..a0dc613
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+/**
+ * An immutable object that stores event happened in the {@link MockIme}.
+ */
+public final class ImeEvent {
+
+ ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, int threadId,
+ boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime,
+ long exitWallTime, @NonNull ImeState enterState, @NonNull ImeState exitState,
+ @NonNull Bundle arguments) {
+ mEventName = eventName;
+ mNestLevel = nestLevel;
+ mThreadName = threadName;
+ mThreadId = threadId;
+ mIsMainThread = isMainThread;
+ mEnterTimestamp = enterTimestamp;
+ mExitTimestamp = exitTimestamp;
+ mEnterWallTime = enterWallTime;
+ mExitWallTime = exitWallTime;
+ mEnterState = enterState;
+ mExitState = exitState;
+ mArguments = arguments;
+ }
+
+ @NonNull
+ final Bundle toBundle() {
+ final Bundle bundle = new Bundle();
+ bundle.putString("mEventName", mEventName);
+ bundle.putInt("mNestLevel", mNestLevel);
+ bundle.putString("mThreadName", mThreadName);
+ bundle.putInt("mThreadId", mThreadId);
+ bundle.putBoolean("mIsMainThread", mIsMainThread);
+ bundle.putLong("mEnterTimestamp", mEnterTimestamp);
+ bundle.putLong("mExitTimestamp", mExitTimestamp);
+ bundle.putLong("mEnterWallTime", mEnterWallTime);
+ bundle.putLong("mExitWallTime", mExitWallTime);
+ bundle.putBundle("mEnterState", mEnterState.toBundle());
+ bundle.putBundle("mExitState", mExitState.toBundle());
+ bundle.putBundle("mArguments", mArguments);
+ return bundle;
+ }
+
+ @NonNull
+ static ImeEvent fromBundle(@NonNull Bundle bundle) {
+ final String eventName = bundle.getString("mEventName");
+ final int nestLevel = bundle.getInt("mNestLevel");
+ final String threadName = bundle.getString("mThreadName");
+ final int threadId = bundle.getInt("mThreadId");
+ final boolean isMainThread = bundle.getBoolean("mIsMainThread");
+ final long enterTimestamp = bundle.getLong("mEnterTimestamp");
+ final long exitTimestamp = bundle.getLong("mExitTimestamp");
+ final long enterWallTime = bundle.getLong("mEnterWallTime");
+ final long exitWallTime = bundle.getLong("mExitWallTime");
+ final ImeState enterState = ImeState.fromBundle(bundle.getBundle("mEnterState"));
+ final ImeState exitState = ImeState.fromBundle(bundle.getBundle("mExitState"));
+ final Bundle arguments = bundle.getBundle("mArguments");
+ return new ImeEvent(eventName, nestLevel, threadName,
+ threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime,
+ enterState, exitState, arguments);
+ }
+
+ /**
+ * Returns a string that represents the type of this event.
+ *
+ * <p>Examples: "onCreate", "onStartInput", ...</p>
+ *
+ * <p>TODO: Use enum type or something like that instead of raw String type.</p>
+ * @return A string that represents the type of this event.
+ */
+ @NonNull
+ public String getEventName() {
+ return mEventName;
+ }
+
+ /**
+ * Returns the nest level of this event.
+ *
+ * <p>For instance, when "showSoftInput" internally calls
+ * "onStartInputView", the event for "onStartInputView" has 1 level higher
+ * nest level than "showSoftInput".</p>
+ */
+ public int getNestLevel() {
+ return mNestLevel;
+ }
+
+ /**
+ * @return Name of the thread, where the event was consumed.
+ */
+ @NonNull
+ public String getThreadName() {
+ return mThreadName;
+ }
+
+ /**
+ * @return Thread ID (TID) of the thread, where the event was consumed.
+ */
+ public int getThreadId() {
+ return mThreadId;
+ }
+
+ /**
+ * @return {@code true} if the event was being consumed in the main thread.
+ */
+ public boolean isMainThread() {
+ return mIsMainThread;
+ }
+
+ /**
+ * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+ * the corresponding event handler was called back.
+ */
+ public long getEnterTimestamp() {
+ return mEnterTimestamp;
+ }
+
+ /**
+ * @return Monotonic time measured by {@link android.os.SystemClock#elapsedRealtimeNanos()} when
+ * the corresponding event handler finished.
+ */
+ public long getExitTimestamp() {
+ return mExitTimestamp;
+ }
+
+ /**
+ * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+ * event handler was called back.
+ */
+ public long getEnterWallTime() {
+ return mEnterWallTime;
+ }
+
+ /**
+ * @return Wall-clock time measured by {@link System#currentTimeMillis()} when the corresponding
+ * event handler finished.
+ */
+ public long getExitWallTime() {
+ return mExitWallTime;
+ }
+
+ /**
+ * @return IME state snapshot taken when the corresponding event handler was called back.
+ */
+ @NonNull
+ public ImeState getEnterState() {
+ return mEnterState;
+ }
+
+ /**
+ * @return IME state snapshot taken when the corresponding event handler finished.
+ */
+ @NonNull
+ public ImeState getExitState() {
+ return mExitState;
+ }
+
+ /**
+ * @return {@link Bundle} that stores parameters passed to the corresponding event handler.
+ */
+ @NonNull
+ public Bundle getArguments() {
+ return mArguments;
+ }
+
+ @NonNull
+ private final String mEventName;
+ private final int mNestLevel;
+ @NonNull
+ private final String mThreadName;
+ private final int mThreadId;
+ private final boolean mIsMainThread;
+ private final long mEnterTimestamp;
+ private final long mExitTimestamp;
+ private final long mEnterWallTime;
+ private final long mExitWallTime;
+ @NonNull
+ private final ImeState mEnterState;
+ @NonNull
+ private final ImeState mExitState;
+ @NonNull
+ private final Bundle mArguments;
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
new file mode 100644
index 0000000..7d021df
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
+import android.view.inputmethod.EditorInfo;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * A utility class that provides basic query operations and wait primitives for a series of
+ * {@link ImeEvent} sent from the {@link MockIme}.
+ *
+ * <p>All public methods are not thread-safe.</p>
+ */
+public final class ImeEventStream {
+
+ @NonNull
+ private final Supplier<ImeEventArray> mEventSupplier;
+ private int mCurrentPosition;
+
+ ImeEventStream(@NonNull Supplier<ImeEventArray> supplier) {
+ this(supplier, 0 /* position */);
+ }
+
+ private ImeEventStream(@NonNull Supplier<ImeEventArray> supplier, int position) {
+ mEventSupplier = supplier;
+ mCurrentPosition = position;
+ }
+
+ /**
+ * Create a copy that starts from the same event position of this stream. Once a copy is created
+ * further event position change on this stream will not affect the copy.
+ *
+ * @return A new copy of this stream
+ */
+ public ImeEventStream copy() {
+ return new ImeEventStream(mEventSupplier, mCurrentPosition);
+ }
+
+ /**
+ * Advances the current event position by skipping events.
+ *
+ * @param length number of events to be skipped
+ * @throws IllegalArgumentException {@code length} is negative
+ */
+ public void skip(@IntRange(from = 0) int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("length cannot be negative: " + length);
+ }
+ mCurrentPosition += length;
+ }
+
+ /**
+ * Advances the current event position to the next to the last position.
+ */
+ public void skipAll() {
+ mCurrentPosition = mEventSupplier.get().mLength;
+ }
+
+ /**
+ * Find the first event that matches the given condition from the current position.
+ *
+ * <p>If there is such an event, this method returns such an event without moving the current
+ * event position.</p>
+ *
+ * <p>If there is such an event, this method returns {@link Optional#empty()} without moving the
+ * current event position.</p>
+ *
+ * @param condition the event condition to be matched
+ * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+ * returned
+ */
+ @NonNull
+ public Optional<ImeEvent> findFirst(Predicate<ImeEvent> condition) {
+ final ImeEventArray latest = mEventSupplier.get();
+ int index = mCurrentPosition;
+ while (true) {
+ if (index >= latest.mLength) {
+ return Optional.empty();
+ }
+ if (condition.test(latest.mArray[index])) {
+ return Optional.of(latest.mArray[index]);
+ }
+ ++index;
+ }
+ }
+
+ /**
+ * Find the first event that matches the given condition from the current position.
+ *
+ * <p>If there is such an event, this method returns such an event and set the current event
+ * position to that event.</p>
+ *
+ * <p>If there is such an event, this method returns {@link Optional#empty()} without moving the
+ * current event position.</p>
+ *
+ * @param condition the event condition to be matched
+ * @return {@link Optional#empty()} if there is no such an event. Otherwise the matched event is
+ * returned
+ */
+ @NonNull
+ public Optional<ImeEvent> seekToFirst(Predicate<ImeEvent> condition) {
+ final ImeEventArray latest = mEventSupplier.get();
+ while (true) {
+ if (mCurrentPosition >= latest.mLength) {
+ return Optional.empty();
+ }
+ if (condition.test(latest.mArray[mCurrentPosition])) {
+ return Optional.of(latest.mArray[mCurrentPosition]);
+ }
+ ++mCurrentPosition;
+ }
+ }
+
+ /**
+ * @return Debug info as a {@link String}.
+ */
+ public String dump() {
+ final ImeEventArray latest = mEventSupplier.get();
+ final StringBuilder sb = new StringBuilder();
+ final SimpleDateFormat dataFormat =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+ sb.append("ImeEventStream:\n");
+ sb.append(" latest: array[").append(latest.mArray.length).append("] + {\n");
+ for (int i = 0; i < latest.mLength; ++i) {
+ final ImeEvent event = latest.mArray[i];
+ if (i == mCurrentPosition) {
+ sb.append(" ======== CurrentPosition ======== \n");
+ }
+ sb.append(" ").append(i).append(" :");
+ if (event != null) {
+ for (int j = 0; j < event.getNestLevel(); ++j) {
+ sb.append(' ');
+ }
+ sb.append('{');
+ sb.append(dataFormat.format(new Date(event.getEnterWallTime())));
+ sb.append(" event=").append(event.getEventName());
+ sb.append(": args=");
+ dumpBundle(sb, event.getArguments());
+ sb.append("},\n");
+ } else {
+ sb.append("{null},\n");
+ }
+ }
+ if (mCurrentPosition >= latest.mLength) {
+ sb.append(" ======== CurrentPosition ======== \n");
+ }
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private static final void dumpBundle(@NonNull StringBuilder sb, @NonNull Bundle bundle) {
+ sb.append('{');
+ boolean first = true;
+ for (String key : bundle.keySet()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(' ');
+ }
+ final Object object = bundle.get(key);
+ sb.append(key);
+ sb.append('=');
+ if (object instanceof EditorInfo) {
+ final EditorInfo info = (EditorInfo) object;
+ sb.append("EditorInfo{packageName=").append(info.packageName);
+ sb.append(" fieldId=").append(info.fieldId);
+ sb.append(" hintText=").append(info.hintText);
+ sb.append(" privateImeOptions=").append(info.privateImeOptions);
+ sb.append("}");
+ } else {
+ sb.append(object);
+ }
+ }
+ sb.append('}');
+ }
+
+ final static class ImeEventArray {
+ @NonNull
+ public final ImeEvent[] mArray;
+ public final int mLength;
+ public ImeEventArray(ImeEvent[] array, int length) {
+ mArray = array;
+ mLength = length;
+ }
+ }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
new file mode 100644
index 0000000..5c1ef24
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.view.inputmethod.InputBinding;
+
+import java.util.Optional;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+
+/**
+ * A set of utility methods to avoid boilerplate code when writing end-to-end tests.
+ */
+public final class ImeEventStreamTestUtils {
+ private static final long TIME_SLICE = 50; // msec
+
+ /**
+ * Cannot be instantiated
+ */
+ private ImeEventStreamTestUtils() {}
+
+ /**
+ * Wait until an event that matches the given {@code condition} is found in the stream.
+ *
+ * <p>When this method succeeds to find an event that matches the given {@code condition}, the
+ * stream position will be set to the next to the found object then the event found is returned.
+ * </p>
+ *
+ * @param stream {@link ImeEventStream} to be checked.
+ * @param condition the event condition to be matched
+ * @param timeout timeout in millisecond
+ * @return {@link ImeEvent} found
+ * @throws TimeoutException when the no event is matched to the given condition within
+ * {@code timeout}
+ */
+ @NonNull
+ public static ImeEvent expectEvent(@NonNull ImeEventStream stream,
+ @NonNull Predicate<ImeEvent> condition, long timeout) throws TimeoutException {
+ try {
+ Optional<ImeEvent> result;
+ while (true) {
+ if (timeout < 0) {
+ throw new TimeoutException(
+ "event not found within the timeout: " + stream.dump());
+ }
+ result = stream.seekToFirst(condition);
+ if (result.isPresent()) {
+ break;
+ }
+ Thread.sleep(TIME_SLICE);
+ timeout -= TIME_SLICE;
+ }
+ final ImeEvent event = result.get();
+ if (event == null) {
+ throw new NullPointerException("found event is null: " + stream.dump());
+ }
+ stream.skip(1);
+ return event;
+ } catch (InterruptedException e) {
+ throw new RuntimeException("expectEvent failed: " + stream.dump(), e);
+ }
+ }
+
+ /**
+ * Wait until an event that matches the given command is consumed by the {@link MockIme}.
+ *
+ * @param stream {@link ImeEventStream} to be checked.
+ * @param command {@link ImeCommand} to be waited for.
+ * @param timeout timeout in millisecond
+ * @return {@link ImeEvent} found
+ * @throws TimeoutException when the no event is matched to the given condition within
+ * {@code timeout}
+ */
+ @NonNull
+ public static ImeEvent expectCommand(@NonNull ImeEventStream stream,
+ @NonNull ImeCommand command, long timeout) throws TimeoutException {
+ final Predicate<ImeEvent> predicate = event -> {
+ if (!TextUtils.equals("onHandleCommand", event.getEventName())) {
+ return false;
+ }
+ final ImeCommand eventCommand =
+ ImeCommand.fromBundle(event.getArguments().getBundle("command"));
+ return eventCommand.getId() == command.getId();
+ };
+ return expectEvent(stream, predicate, timeout);
+ }
+
+ /**
+ * Assert that an event that matches the given {@code condition} will no be found in the stream
+ * within the given {@code timeout}.
+ *
+ * <p>Fails with {@link junit.framework.Assert#fail} if such an event is found within the given
+ * {@code timeout}.</p>
+ *
+ * <p>When this method succeeds, the stream position will not change.</p>
+ *
+ * @param stream {@link ImeEventStream} to be checked.
+ * @param condition the event condition to be matched
+ * @param timeout timeout in millisecond
+ */
+ public static void notExpectEvent(@NonNull ImeEventStream stream,
+ @NonNull Predicate<ImeEvent> condition, long timeout) {
+ try {
+ while (true) {
+ if (timeout < 0) {
+ return;
+ }
+ if (stream.findFirst(condition).isPresent()) {
+ throw new AssertionError("notExpectEvent failed: " + stream.dump());
+ }
+ Thread.sleep(TIME_SLICE);
+ timeout -= TIME_SLICE;
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
+ }
+ }
+
+ /**
+ * A specialized version of {@link #expectEvent(ImeEventStream, Predicate, long)} to wait for
+ * {@link android.view.inputmethod.InputMethod#bindInput(InputBinding)}.
+ *
+ * @param stream {@link ImeEventStream} to be checked.
+ * @param targetProcessPid PID to be matched to {@link InputBinding#getPid()}
+ * @param timeout timeout in millisecond
+ * @throws TimeoutException when "bindInput" is not called within {@code timeout} msec
+ */
+ public static void expectBindInput(@NonNull ImeEventStream stream, int targetProcessPid,
+ long timeout) throws TimeoutException {
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("bindInput", event.getEventName())) {
+ return false;
+ }
+ final InputBinding binding = event.getArguments().getParcelable("binding");
+ return binding.getPid() == targetProcessPid;
+ }, timeout);
+ }
+
+ /**
+ * Waits until {@code MockIme} does not send {@code "onInputViewLayoutChanged"} event
+ * for a certain period of time ({@code stableThresholdTime} msec).
+ *
+ * <p>When this returns non-null {@link ImeLayoutInfo}, the stream position will be set to
+ * the next event of the returned layout event. Otherwise this method does not change stream
+ * position.</p>
+ * @param stream {@link ImeEventStream} to be checked.
+ * @param stableThresholdTime threshold time to consider that {@link MockIme}'s layout is
+ * stable, in millisecond
+ * @return last {@link ImeLayoutInfo} if {@link MockIme} sent one or more
+ * {@code "onInputViewLayoutChanged"} event. Otherwise {@code null}
+ */
+ public static ImeLayoutInfo waitForInputViewLayoutStable(@NonNull ImeEventStream stream,
+ long stableThresholdTime) {
+ ImeLayoutInfo lastLayout = null;
+ final Predicate<ImeEvent> layoutFilter =
+ event -> "onInputViewLayoutChanged".equals(event.getEventName());
+ try {
+ long deadline = SystemClock.elapsedRealtime() + stableThresholdTime;
+ while (true) {
+ if (deadline < SystemClock.elapsedRealtime()) {
+ return lastLayout;
+ }
+ final Optional<ImeEvent> event = stream.seekToFirst(layoutFilter);
+ if (event.isPresent()) {
+ // Remember the last event and extend the deadline again.
+ lastLayout = ImeLayoutInfo.readFromBundle(event.get().getArguments());
+ deadline = SystemClock.elapsedRealtime() + stableThresholdTime;
+ stream.skip(1);
+ }
+ Thread.sleep(TIME_SLICE);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("notExpectEvent failed: " + stream.dump(), e);
+ }
+ }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
new file mode 100644
index 0000000..9c729b3
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeLayoutInfo.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowInsets;
+
+/**
+ * A collection of layout-related information when
+ * {@link View.OnLayoutChangeListener#onLayoutChange(View, int, int, int, int, int, int, int, int)}
+ * is called back for the input view (the view returned from {@link MockIme#onCreateInputView()}).
+ */
+public final class ImeLayoutInfo {
+
+ private final static String NEW_LAYOUT_KEY = "newLayout";
+ private final static String OLD_LAYOUT_KEY = "oldLayout";
+ private final static String VIEW_ORIGIN_ON_SCREEN_KEY = "viewOriginOnScreen";
+ private final static String DISPLAY_SIZE_KEY = "displaySize";
+ private final static String SYSTEM_WINDOW_INSET_KEY = "systemWindowInset";
+ private final static String STABLE_INSET_KEY = "stableInset";
+
+ @NonNull
+ private final Rect mNewLayout;
+ @NonNull
+ private final Rect mOldLayout;
+ @Nullable
+ private Point mViewOriginOnScreen;
+ @Nullable
+ private Point mDisplaySize;
+ @Nullable
+ private Rect mSystemWindowInset;
+ @Nullable
+ private Rect mStableInset;
+
+ /**
+ * Returns the bounding box of the {@link View} passed to
+ * {@link android.inputmethodservice.InputMethodService#onCreateInputView()} in screen
+ * coordinates.
+ *
+ * <p>Currently this method assumes that no {@link View} in the hierarchy uses
+ * transformations such as {@link View#setRotation(float)}.</p>
+ *
+ * @return Region in screen coordinates.
+ */
+ @Nullable
+ public Rect getInputViewBoundsInScreen() {
+ return new Rect(
+ mViewOriginOnScreen.x, mViewOriginOnScreen.y,
+ mViewOriginOnScreen.x + mNewLayout.width(),
+ mViewOriginOnScreen.y + mNewLayout.height());
+ }
+
+ /**
+ * Returns the screen area in screen coordinates that does not overlap with the system
+ * window inset, which represents the area of a full-screen window that is partially or
+ * fully obscured by the status bar, navigation bar, IME or other system windows.
+ *
+ * <p>May return {@code null} when this information is not yet ready.</p>
+ *
+ * @return Region in screen coordinates. {@code null} when it is not available
+ *
+ * @see WindowInsets#hasSystemWindowInsets()
+ * @see WindowInsets#getSystemWindowInsetBottom()
+ * @see WindowInsets#getSystemWindowInsetLeft()
+ * @see WindowInsets#getSystemWindowInsetRight()
+ * @see WindowInsets#getSystemWindowInsetTop()
+ */
+ @Nullable
+ public Rect getScreenRectWithoutSystemWindowInset() {
+ if (mDisplaySize == null) {
+ return null;
+ }
+ if (mSystemWindowInset == null) {
+ return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+ }
+ return new Rect(mSystemWindowInset.left, mSystemWindowInset.top,
+ mDisplaySize.x - mSystemWindowInset.right,
+ mDisplaySize.y - mSystemWindowInset.bottom);
+ }
+
+ /**
+ * Returns the screen area in screen coordinates that does not overlap with the stable
+ * inset, which represents the area of a full-screen window that <b>may</b> be partially or
+ * fully obscured by the system UI elements.
+ *
+ * <p>May return {@code null} when this information is not yet ready.</p>
+ *
+ * @return Region in screen coordinates. {@code null} when it is not available
+ *
+ * @see WindowInsets#hasStableInsets()
+ * @see WindowInsets#getStableInsetBottom()
+ * @see WindowInsets#getStableInsetLeft()
+ * @see WindowInsets#getStableInsetRight()
+ * @see WindowInsets#getStableInsetTop()
+ */
+ @Nullable
+ public Rect getScreenRectWithoutStableInset() {
+ if (mDisplaySize == null) {
+ return null;
+ }
+ if (mStableInset == null) {
+ return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
+ }
+ return new Rect(mStableInset.left, mStableInset.top,
+ mDisplaySize.x - mStableInset.right,
+ mDisplaySize.y - mStableInset.bottom);
+ }
+
+ ImeLayoutInfo(@NonNull Rect newLayout, @NonNull Rect oldLayout,
+ @NonNull Point viewOriginOnScreen, @Nullable Point displaySize,
+ @Nullable Rect systemWindowInset, @Nullable Rect stableInset) {
+ mNewLayout = new Rect(newLayout);
+ mOldLayout = new Rect(oldLayout);
+ mViewOriginOnScreen = new Point(viewOriginOnScreen);
+ mDisplaySize = new Point(displaySize);
+ mSystemWindowInset = systemWindowInset;
+ mStableInset = stableInset;
+ }
+
+ void writeToBundle(@NonNull Bundle bundle) {
+ bundle.putParcelable(NEW_LAYOUT_KEY, mNewLayout);
+ bundle.putParcelable(OLD_LAYOUT_KEY, mOldLayout);
+ bundle.putParcelable(VIEW_ORIGIN_ON_SCREEN_KEY, mViewOriginOnScreen);
+ bundle.putParcelable(DISPLAY_SIZE_KEY, mDisplaySize);
+ bundle.putParcelable(SYSTEM_WINDOW_INSET_KEY, mSystemWindowInset);
+ bundle.putParcelable(STABLE_INSET_KEY, mStableInset);
+ }
+
+ static ImeLayoutInfo readFromBundle(@NonNull Bundle bundle) {
+ final Rect newLayout = bundle.getParcelable(NEW_LAYOUT_KEY);
+ final Rect oldLayout = bundle.getParcelable(OLD_LAYOUT_KEY);
+ final Point viewOrigin = bundle.getParcelable(VIEW_ORIGIN_ON_SCREEN_KEY);
+ final Point displaySize = bundle.getParcelable(DISPLAY_SIZE_KEY);
+ final Rect systemWindowInset = bundle.getParcelable(SYSTEM_WINDOW_INSET_KEY);
+ final Rect stableInset = bundle.getParcelable(STABLE_INSET_KEY);
+
+ return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
+ stableInset);
+ }
+
+ static ImeLayoutInfo fromLayoutListenerCallback(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ final Rect newLayout = new Rect(left, top, right, bottom);
+ final Rect oldLayout = new Rect(oldLeft, oldTop, oldRight, oldBottom);
+ final int[] viewOriginArray = new int[2];
+ v.getLocationOnScreen(viewOriginArray);
+ final Point viewOrigin = new Point(viewOriginArray[0], viewOriginArray[1]);
+ final Display display = v.getDisplay();
+ final Point displaySize;
+ if (display != null) {
+ displaySize = new Point();
+ display.getRealSize(displaySize);
+ } else {
+ displaySize = null;
+ }
+ final WindowInsets windowInsets = v.getRootWindowInsets();
+ final Rect systemWindowInset;
+ if (windowInsets != null && windowInsets.hasSystemWindowInsets()) {
+ systemWindowInset = new Rect(
+ windowInsets.getSystemWindowInsetLeft(), windowInsets.getSystemWindowInsetTop(),
+ windowInsets.getSystemWindowInsetRight(),
+ windowInsets.getSystemWindowInsetBottom());
+ } else {
+ systemWindowInset = null;
+ }
+ final Rect stableInset;
+ if (windowInsets != null && windowInsets.hasStableInsets()) {
+ stableInset = new Rect(
+ windowInsets.getStableInsetLeft(), windowInsets.getStableInsetTop(),
+ windowInsets.getStableInsetRight(), windowInsets.getStableInsetBottom());
+ } else {
+ stableInset = null;
+ }
+ return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
+ stableInset);
+ }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
new file mode 100644
index 0000000..7b79c26
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * An immutable data store to control the behavior of {@link MockIme}.
+ */
+public class ImeSettings {
+
+ @NonNull
+ private final String mEventCallbackActionName;
+
+ private static final String BACKGROUND_COLOR_KEY = "BackgroundColor";
+ private static final String NAVIGATION_BAR_COLOR_KEY = "NavigationBarColor";
+ private static final String INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET =
+ "InputViewHeightWithoutSystemWindowInset";
+ private static final String WINDOW_FLAGS = "WindowFlags";
+ private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
+ private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
+ private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
+ private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
+ "HardKeyboardConfigurationBehaviorAllowed";
+
+ @NonNull
+ private final PersistableBundle mBundle;
+
+ ImeSettings(@NonNull Parcel parcel) {
+ mEventCallbackActionName = parcel.readString();
+ mBundle = parcel.readPersistableBundle();
+ }
+
+ @Nullable
+ String getEventCallbackActionName() {
+ return mEventCallbackActionName;
+ }
+
+ public boolean fullscreenModeAllowed(boolean defaultValue) {
+ return mBundle.getBoolean(FULLSCREEN_MODE_ALLOWED, defaultValue);
+ }
+
+ @ColorInt
+ public int getBackgroundColor(@ColorInt int defaultColor) {
+ return mBundle.getInt(BACKGROUND_COLOR_KEY, defaultColor);
+ }
+
+ public boolean hasNavigationBarColor() {
+ return mBundle.keySet().contains(NAVIGATION_BAR_COLOR_KEY);
+ }
+
+ @ColorInt
+ public int getNavigationBarColor() {
+ return mBundle.getInt(NAVIGATION_BAR_COLOR_KEY);
+ }
+
+ public int getInputViewHeightWithoutSystemWindowInset(int defaultHeight) {
+ return mBundle.getInt(INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET, defaultHeight);
+ }
+
+ public int getWindowFlags(int defaultFlags) {
+ return mBundle.getInt(WINDOW_FLAGS, defaultFlags);
+ }
+
+ public int getWindowFlagsMask(int defaultFlags) {
+ return mBundle.getInt(WINDOW_FLAGS_MASK, defaultFlags);
+ }
+
+ public int getInputViewSystemUiVisibility(int defaultFlags) {
+ return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags);
+ }
+
+ public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) {
+ return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue);
+ }
+
+ static void writeToParcel(@NonNull Parcel parcel, @NonNull String eventCallbackActionName,
+ @Nullable Builder builder) {
+ parcel.writeString(eventCallbackActionName);
+ if (builder != null) {
+ parcel.writePersistableBundle(builder.mBundle);
+ } else {
+ parcel.writePersistableBundle(PersistableBundle.EMPTY);
+ }
+ }
+
+ /**
+ * The builder class for {@link ImeSettings}.
+ */
+ public static final class Builder {
+ private final PersistableBundle mBundle = new PersistableBundle();
+
+ /**
+ * Controls whether fullscreen mode is allowed or not.
+ *
+ * <p>By default, fullscreen mode is not allowed in {@link MockIme}.</p>
+ *
+ * @param allowed {@code true} if fullscreen mode is allowed
+ * @see MockIme#onEvaluateFullscreenMode()
+ */
+ public Builder setFullscreenModeAllowed(boolean allowed) {
+ mBundle.putBoolean(FULLSCREEN_MODE_ALLOWED, allowed);
+ return this;
+ }
+
+ /**
+ * Sets the background color of the {@link MockIme}.
+ * @param color background color to be used
+ */
+ public Builder setBackgroundColor(@ColorInt int color) {
+ mBundle.putInt(BACKGROUND_COLOR_KEY, color);
+ return this;
+ }
+
+ /**
+ * Sets the color to be passed to {@link android.view.Window#setNavigationBarColor(int)}.
+ *
+ * @param color color to be passed to {@link android.view.Window#setNavigationBarColor(int)}
+ * @see android.view.View
+ */
+ public Builder setNavigationBarColor(@ColorInt int color) {
+ mBundle.putInt(NAVIGATION_BAR_COLOR_KEY, color);
+ return this;
+ }
+
+ /**
+ * Sets the input view height measured from the bottom system window inset.
+ * @param height height of the soft input view. This does not include the system window
+ * inset such as navigation bar
+ */
+ public Builder setInputViewHeightWithoutSystemWindowInset(int height) {
+ mBundle.putInt(INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET, height);
+ return this;
+ }
+
+ /**
+ * Sets window flags to be specified to {@link android.view.Window#setFlags(int, int)} of
+ * the main {@link MockIme} window.
+ *
+ * <p>When {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} is set,
+ * {@link MockIme} tries to render the navigation bar by itself.</p>
+ *
+ * @param flags flags to be specified
+ * @param flagsMask mask bits that specify what bits need to be cleared before setting
+ * {@code flags}
+ * @see android.view.WindowManager
+ */
+ public Builder setWindowFlags(int flags, int flagsMask) {
+ mBundle.putInt(WINDOW_FLAGS, flags);
+ mBundle.putInt(WINDOW_FLAGS_MASK, flagsMask);
+ return this;
+ }
+
+ /**
+ * Sets flags to be specified to {@link android.view.View#setSystemUiVisibility(int)} of
+ * the main soft input view (the returned view from {@link MockIme#onCreateInputView()}).
+ *
+ * @param visibilityFlags flags to be specified
+ * @see android.view.View
+ */
+ public Builder setInputViewSystemUiVisibility(int visibilityFlags) {
+ mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags);
+ return this;
+ }
+
+ /**
+ * Controls whether {@link MockIme} is allowed to change the behavior based on
+ * {@link android.content.res.Configuration#keyboard} and
+ * {@link android.content.res.Configuration#hardKeyboardHidden}.
+ *
+ * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as
+ * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and
+ * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)}
+ * change their behaviors when a hardware keyboard is attached. This is confusing when
+ * writing tests so by default {@link MockIme} tries to cancel those behaviors. This
+ * settings re-enables such a behavior.</p>
+ *
+ * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when
+ * a hardware keyboard is attached
+ *
+ * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()
+ * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)
+ */
+ public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) {
+ mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed);
+ return this;
+ }
+ }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
new file mode 100644
index 0000000..0b7fd04
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+/**
+ * An immutable object that stores several runtime state of {@link MockIme}.
+ */
+public final class ImeState {
+ private final boolean mHasInputBinding;
+ private final boolean mHasDummyInputConnection;
+
+ /**
+ * @return {@code true} if {@link MockIme#getCurrentInputBinding()} returned non-null
+ * {@link android.view.inputmethod.InputBinding} when this snapshot was taken.
+ */
+ public boolean hasInputBinding() { return mHasInputBinding; }
+
+ /**
+ * @return {@code true} if {@link MockIme#getCurrentInputConnection()} returned non-dummy
+ * {@link android.view.inputmethod.InputConnection} when this snapshot was taken.
+ */
+ public boolean hasDummyInputConnection() { return mHasDummyInputConnection; }
+
+ ImeState(boolean hasInputBinding, boolean hasDummyInputConnection) {
+ mHasInputBinding = hasInputBinding;
+ mHasDummyInputConnection = hasDummyInputConnection;
+ }
+
+ @NonNull
+ final Bundle toBundle() {
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean("mHasInputBinding", mHasInputBinding);
+ bundle.putBoolean("mHasDummyInputConnection", mHasDummyInputConnection);
+ return bundle;
+ }
+
+ @NonNull
+ static ImeState fromBundle(@NonNull Bundle bundle) {
+ final boolean hasInputBinding = bundle.getBoolean("mHasInputBinding");
+ final boolean hasDummyInputConnection = bundle.getBoolean("mHasDummyInputConnection");
+ return new ImeState(hasInputBinding, hasDummyInputConnection);
+ }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
new file mode 100644
index 0000000..79c35f1
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import static com.android.cts.mockime.MockImeSession.MOCK_IME_SETTINGS_FILE;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.support.annotation.AnyThread;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Mock IME for end-to-end tests.
+ */
+public final class MockIme extends InputMethodService {
+
+ private static final String TAG = "MockIme";
+
+ static ComponentName getComponentName(@NonNull String packageName) {
+ return new ComponentName(packageName, MockIme.class.getName());
+ }
+
+ static String getImeId(@NonNull String packageName) {
+ return new ComponentName(packageName, MockIme.class.getName()).flattenToShortString();
+ }
+
+ static String getCommandActionName(@NonNull String eventActionName) {
+ return eventActionName + ".command";
+ }
+
+ private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
+
+ private final Handler mMainHandler = new Handler();
+
+ private static final class CommandReceiver extends BroadcastReceiver {
+ @NonNull
+ private final String mActionName;
+ @NonNull
+ private final Consumer<ImeCommand> mOnReceiveCommand;
+
+ public CommandReceiver(@NonNull String actionName,
+ @NonNull Consumer<ImeCommand> onReceiveCommand) {
+ mActionName = actionName;
+ mOnReceiveCommand = onReceiveCommand;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TextUtils.equals(mActionName, intent.getAction())) {
+ mOnReceiveCommand.accept(ImeCommand.fromBundle(intent.getExtras()));
+ }
+ }
+ }
+
+ @WorkerThread
+ private void onReceiveCommand(@NonNull ImeCommand command) {
+ getTracer().onReceiveCommand(command, () -> {
+ if (command.shouldDispatchToMainThread()) {
+ mMainHandler.post(() -> onHandleCommand(command));
+ } else {
+ onHandleCommand(command);
+ }
+ });
+ }
+
+ @AnyThread
+ private void onHandleCommand(@NonNull ImeCommand command) {
+ getTracer().onHandleCommand(command, () -> {
+ if (command.shouldDispatchToMainThread()) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException("command " + command
+ + " should be handled on the main thread");
+ }
+ switch (command.getName()) {
+ case "commitText": {
+ final CharSequence text = command.getExtras().getString("text");
+ final int newCursorPosition =
+ command.getExtras().getInt("newCursorPosition");
+ getCurrentInputConnection().commitText(text, newCursorPosition);
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ @Nullable
+ private CommandReceiver mCommandReceiver;
+
+ @Nullable
+ private ImeSettings mSettings;
+
+ private final AtomicReference<String> mImeEventActionName = new AtomicReference<>();
+
+ @Nullable
+ String getImeEventActionName() {
+ return mImeEventActionName.get();
+ }
+
+ private class MockInputMethodImpl extends InputMethodImpl {
+ @Override
+ public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+ getTracer().showSoftInput(flags, resultReceiver,
+ () -> super.showSoftInput(flags, resultReceiver));
+ }
+
+ @Override
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+ getTracer().hideSoftInput(flags, resultReceiver,
+ () -> super.hideSoftInput(flags, resultReceiver));
+ }
+
+ @Override
+ public void attachToken(IBinder token) {
+ getTracer().attachToken(token, () -> super.attachToken(token));
+ }
+
+ @Override
+ public void bindInput(InputBinding binding) {
+ getTracer().bindInput(binding, () -> super.bindInput(binding));
+ }
+
+ @Override
+ public void unbindInput() {
+ getTracer().unbindInput(() -> super.unbindInput());
+ }
+ }
+
+ @Nullable
+ private ImeSettings readSettings() {
+ try (InputStream is = openFileInput(MOCK_IME_SETTINGS_FILE)) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ final byte[] buffer = new byte[4096];
+ while (true) {
+ final int numRead = is.read(buffer);
+ if (numRead <= 0) {
+ break;
+ }
+ parcel.unmarshall(buffer, 0, numRead);
+ }
+ parcel.setDataPosition(0);
+ return new ImeSettings(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ getTracer().onCreate(() -> {
+ super.onCreate();
+ mSettings = readSettings();
+ if (mSettings == null) {
+ throw new IllegalStateException("Settings file is not found. "
+ + "Make sure MockImeSession.create() is used to launch Mock IME.");
+ }
+
+ mHandlerThread.start();
+ final String actionName = getCommandActionName(mSettings.getEventCallbackActionName());
+ mCommandReceiver = new CommandReceiver(actionName, this::onReceiveCommand);
+ registerReceiver(mCommandReceiver,
+ new IntentFilter(actionName), null /* broadcastPermission */,
+ new Handler(mHandlerThread.getLooper()));
+
+ mImeEventActionName.set(mSettings.getEventCallbackActionName());
+ final int windowFlags = mSettings.getWindowFlags(0);
+ final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
+ if (windowFlags != 0 || windowFlagsMask != 0) {
+ final int prevFlags = getWindow().getWindow().getAttributes().flags;
+ getWindow().getWindow().setFlags(windowFlags, windowFlagsMask);
+ // For some reasons, seems that we need to post another requestLayout() when
+ // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS bit is changed.
+ // TODO: Investigate the reason.
+ if ((windowFlagsMask & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+ final boolean hadFlag = (prevFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ final boolean hasFlag = (windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ if (hadFlag != hasFlag) {
+ final View decorView = getWindow().getWindow().getDecorView();
+ decorView.post(() -> decorView.requestLayout());
+ }
+ }
+ }
+
+ if (mSettings.hasNavigationBarColor()) {
+ getWindow().getWindow().setNavigationBarColor(mSettings.getNavigationBarColor());
+ }
+ });
+ }
+
+ @Override
+ public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) {
+ getTracer().onConfigureWindow(win, isFullscreen, isCandidatesOnly,
+ () -> super.onConfigureWindow(win, isFullscreen, isCandidatesOnly));
+ }
+
+ @Override
+ public boolean onEvaluateFullscreenMode() {
+ return getTracer().onEvaluateFullscreenMode(() ->
+ mSettings.fullscreenModeAllowed(false) && super.onEvaluateFullscreenMode());
+ }
+
+ private static final class KeyboardLayoutView extends LinearLayout {
+ @NonNull
+ private final ImeSettings mSettings;
+ @NonNull
+ private final View.OnLayoutChangeListener mLayoutListener;
+
+ public KeyboardLayoutView(Context context, @NonNull ImeSettings imeSettings,
+ @Nullable Consumer<ImeLayoutInfo> onInputViewLayoutChangedCallback) {
+ super(context);
+
+ mSettings = imeSettings;
+
+ setOrientation(VERTICAL);
+
+ final int defaultBackgroundColor =
+ getResources().getColor(android.R.color.holo_orange_dark, null);
+ setBackgroundColor(mSettings.getBackgroundColor(defaultBackgroundColor));
+
+ final int mainSpacerHeight = mSettings.getInputViewHeightWithoutSystemWindowInset(
+ LayoutParams.WRAP_CONTENT);
+ {
+ final RelativeLayout layout = new RelativeLayout(getContext());
+ final TextView textView = new TextView(getContext());
+ final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT);
+ params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
+ textView.setLayoutParams(params);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
+ textView.setGravity(Gravity.CENTER);
+ textView.setText(getImeId(getContext().getPackageName()));
+ layout.addView(textView);
+ addView(layout, LayoutParams.MATCH_PARENT, mainSpacerHeight);
+ }
+
+ final int systemUiVisibility = mSettings.getInputViewSystemUiVisibility(0);
+ if (systemUiVisibility != 0) {
+ setSystemUiVisibility(systemUiVisibility);
+ }
+
+ mLayoutListener = (View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) ->
+ onInputViewLayoutChangedCallback.accept(ImeLayoutInfo.fromLayoutListenerCallback(
+ v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom));
+ this.addOnLayoutChangeListener(mLayoutListener);
+ }
+
+ private void updateBottomPaddingIfNecessary(int newPaddingBottom) {
+ if (getPaddingBottom() != newPaddingBottom) {
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), newPaddingBottom);
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (insets.isConsumed()
+ || (getSystemUiVisibility() & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0) {
+ // In this case we are not interested in consuming NavBar region.
+ // Make sure that the bottom padding is empty.
+ updateBottomPaddingIfNecessary(0);
+ return insets;
+ }
+
+ // In some cases the bottom system window inset is not a navigation bar. Wear devices
+ // that have bottom chin are examples. For now, assume that it's a navigation bar if it
+ // has the same height as the root window's stable bottom inset.
+ final WindowInsets rootWindowInsets = getRootWindowInsets();
+ if (rootWindowInsets != null && (rootWindowInsets.getStableInsetBottom() !=
+ insets.getSystemWindowInsetBottom())) {
+ // This is probably not a NavBar.
+ updateBottomPaddingIfNecessary(0);
+ return insets;
+ }
+
+ final int possibleNavBarHeight = insets.getSystemWindowInsetBottom();
+ updateBottomPaddingIfNecessary(possibleNavBarHeight);
+ return possibleNavBarHeight <= 0
+ ? insets
+ : insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(),
+ insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(),
+ 0 /* bottom */);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeOnLayoutChangeListener(mLayoutListener);
+ }
+ }
+
+ private void onInputViewLayoutChanged(@NonNull ImeLayoutInfo layoutInfo) {
+ getTracer().onInputViewLayoutChanged(layoutInfo, () -> {});
+ }
+
+ @Override
+ public View onCreateInputView() {
+ return getTracer().onCreateInputView(() ->
+ new KeyboardLayoutView(this, mSettings, this::onInputViewLayoutChanged));
+ }
+
+ @Override
+ public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+ getTracer().onStartInput(editorInfo, restarting,
+ () -> super.onStartInput(editorInfo, restarting));
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+ getTracer().onStartInputView(editorInfo, restarting,
+ () -> super.onStartInputView(editorInfo, restarting));
+ }
+
+ @Override
+ public void onFinishInputView(boolean finishingInput) {
+ getTracer().onFinishInputView(finishingInput,
+ () -> super.onFinishInputView(finishingInput));
+ }
+
+ @Override
+ public void onFinishInput() {
+ getTracer().onFinishInput(() -> super.onFinishInput());
+ }
+
+ @CallSuper
+ public boolean onEvaluateInputViewShown() {
+ return getTracer().onEvaluateInputViewShown(() -> {
+ // onShowInputRequested() is indeed @CallSuper so we always call this, even when the
+ // result is ignored.
+ final boolean originalResult = super.onEvaluateInputViewShown();
+ if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+ final Configuration config = getResources().getConfiguration();
+ if (config.keyboard != Configuration.KEYBOARD_NOKEYS
+ && config.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES) {
+ // Override the behavior of InputMethodService#onEvaluateInputViewShown()
+ return true;
+ }
+ }
+ return originalResult;
+ });
+ }
+
+ @Override
+ public boolean onShowInputRequested(int flags, boolean configChange) {
+ return getTracer().onShowInputRequested(flags, configChange, () -> {
+ // onShowInputRequested() is not marked with @CallSuper, but just in case.
+ final boolean originalResult = super.onShowInputRequested(flags, configChange);
+ if (!mSettings.getHardKeyboardConfigurationBehaviorAllowed(false)) {
+ if ((flags & InputMethod.SHOW_EXPLICIT) == 0
+ && getResources().getConfiguration().keyboard
+ != Configuration.KEYBOARD_NOKEYS) {
+ // Override the behavior of InputMethodService#onShowInputRequested()
+ return true;
+ }
+ }
+ return originalResult;
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ getTracer().onDestroy(() -> {
+ super.onDestroy();
+ unregisterReceiver(mCommandReceiver);
+ mHandlerThread.quitSafely();
+ });
+ }
+
+ @Override
+ public AbstractInputMethodImpl onCreateInputMethodInterface() {
+ return getTracer().onCreateInputMethodInterface(() -> new MockInputMethodImpl());
+ }
+
+ private final ThreadLocal<Tracer> mThreadLocalTracer = new ThreadLocal<>();
+
+ private Tracer getTracer() {
+ Tracer tracer = mThreadLocalTracer.get();
+ if (tracer == null) {
+ tracer = new Tracer(this);
+ mThreadLocalTracer.set(tracer);
+ }
+ return tracer;
+ }
+
+ @NonNull
+ private ImeState getState() {
+ final boolean hasInputBinding = getCurrentInputBinding() != null;
+ final boolean hasDummyInputConnectionConnection =
+ !hasInputBinding
+ || getCurrentInputConnection() == getCurrentInputBinding().getConnection();
+ return new ImeState(hasInputBinding, hasDummyInputConnectionConnection);
+ }
+
+ /**
+ * Event tracing helper class for {@link MockIme}.
+ */
+ private static final class Tracer {
+
+ @NonNull
+ private final MockIme mIme;
+
+ private final int mThreadId = Process.myTid();
+
+ @NonNull
+ private final String mThreadName =
+ Thread.currentThread().getName() != null ? Thread.currentThread().getName() : "";
+
+ private final boolean mIsMainThread =
+ Looper.getMainLooper().getThread() == Thread.currentThread();
+
+ private int mNestLevel = 0;
+
+ private String mImeEventActionName;
+
+ public Tracer(@NonNull MockIme mockIme) {
+ mIme = mockIme;
+ }
+
+ private void sendEventInternal(@NonNull ImeEvent event) {
+ final Intent intent = new Intent();
+ intent.setPackage(mIme.getPackageName());
+ if (mImeEventActionName == null) {
+ mImeEventActionName = mIme.getImeEventActionName();
+ }
+ if (mImeEventActionName == null) {
+ Log.e(TAG, "Tracer cannot be used before onCreate()");
+ return;
+ }
+ intent.setAction(mImeEventActionName);
+ intent.putExtras(event.toBundle());
+ mIme.sendBroadcast(intent);
+ }
+
+ private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable) {
+ recordEventInternal(eventName, runnable, new Bundle());
+ }
+
+ private void recordEventInternal(@NonNull String eventName, @NonNull Runnable runnable,
+ @NonNull Bundle arguments) {
+ recordEventInternal(eventName, () -> { runnable.run(); return null; }, arguments);
+ }
+
+ private <T> T recordEventInternal(@NonNull String eventName,
+ @NonNull Supplier<T> supplier) {
+ return recordEventInternal(eventName, supplier, new Bundle());
+ }
+
+ private <T> T recordEventInternal(@NonNull String eventName,
+ @NonNull Supplier<T> supplier, @NonNull Bundle arguments) {
+ final ImeState enterState = mIme.getState();
+ final long enterTimestamp = SystemClock.elapsedRealtimeNanos();
+ final long enterWallTime = System.currentTimeMillis();
+ final int nestLevel = mNestLevel;
+ ++mNestLevel;
+ T result;
+ try {
+ result = supplier.get();
+ } finally {
+ --mNestLevel;
+ }
+ final long exitTimestamp = SystemClock.elapsedRealtimeNanos();
+ final long exitWallTime = System.currentTimeMillis();
+ final ImeState exitState = mIme.getState();
+ sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
+ mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime,
+ exitWallTime, enterState, exitState, arguments));
+ return result;
+ }
+
+ public void onCreate(@NonNull Runnable runnable) {
+ recordEventInternal("onCreate", runnable);
+ }
+
+ public void onConfigureWindow(Window win, boolean isFullscreen,
+ boolean isCandidatesOnly, @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putBoolean("isFullscreen", isFullscreen);
+ arguments.putBoolean("isCandidatesOnly", isCandidatesOnly);
+ recordEventInternal("onConfigureWindow", runnable, arguments);
+ }
+
+ public boolean onEvaluateFullscreenMode(@NonNull BooleanSupplier supplier) {
+ return recordEventInternal("onEvaluateFullscreenMode", supplier::getAsBoolean);
+ }
+
+ public boolean onEvaluateInputViewShown(@NonNull BooleanSupplier supplier) {
+ return recordEventInternal("onEvaluateInputViewShown", supplier::getAsBoolean);
+ }
+
+ public View onCreateInputView(@NonNull Supplier<View> supplier) {
+ return recordEventInternal("onCreateInputView", supplier);
+ }
+
+ public void onStartInput(EditorInfo editorInfo, boolean restarting,
+ @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putParcelable("editorInfo", editorInfo);
+ arguments.putBoolean("restarting", restarting);
+ recordEventInternal("onStartInput", runnable, arguments);
+ }
+
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting,
+ @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putParcelable("editorInfo", editorInfo);
+ arguments.putBoolean("restarting", restarting);
+ recordEventInternal("onStartInputView", runnable, arguments);
+ }
+
+ public void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putBoolean("finishingInput", finishingInput);
+ recordEventInternal("onFinishInputView", runnable, arguments);
+ }
+
+ public void onFinishInput(@NonNull Runnable runnable) {
+ recordEventInternal("onFinishInput", runnable);
+ }
+
+ public boolean onShowInputRequested(int flags, boolean configChange,
+ @NonNull BooleanSupplier supplier) {
+ final Bundle arguments = new Bundle();
+ arguments.putInt("flags", flags);
+ arguments.putBoolean("configChange", configChange);
+ return recordEventInternal("onShowInputRequested", supplier::getAsBoolean, arguments);
+ }
+
+ public void onDestroy(@NonNull Runnable runnable) {
+ recordEventInternal("onDestroy", runnable);
+ }
+
+ public void attachToken(IBinder token, @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putBinder("token", token);
+ recordEventInternal("attachToken", runnable, arguments);
+ }
+
+ public void bindInput(InputBinding binding, @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putParcelable("binding", binding);
+ recordEventInternal("bindInput", runnable, arguments);
+ }
+
+ public void unbindInput(@NonNull Runnable runnable) {
+ recordEventInternal("unbindInput", runnable);
+ }
+
+ public void showSoftInput(int flags, ResultReceiver resultReceiver,
+ @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putInt("flags", flags);
+ arguments.putParcelable("resultReceiver", resultReceiver);
+ recordEventInternal("showSoftInput", runnable, arguments);
+ }
+
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver,
+ @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putInt("flags", flags);
+ arguments.putParcelable("resultReceiver", resultReceiver);
+ recordEventInternal("hideSoftInput", runnable, arguments);
+ }
+
+ public AbstractInputMethodImpl onCreateInputMethodInterface(
+ @NonNull Supplier<AbstractInputMethodImpl> supplier) {
+ return recordEventInternal("onCreateInputMethodInterface", supplier);
+ }
+
+ public void onReceiveCommand(
+ @NonNull ImeCommand command, @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putBundle("command", command.toBundle());
+ recordEventInternal("onReceiveCommand", runnable, arguments);
+ }
+
+ public void onHandleCommand(
+ @NonNull ImeCommand command, @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ arguments.putBundle("command", command.toBundle());
+ recordEventInternal("onHandleCommand", runnable, arguments);
+ }
+
+ public void onInputViewLayoutChanged(@NonNull ImeLayoutInfo imeLayoutInfo,
+ @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ imeLayoutInfo.writeToBundle(arguments);
+ recordEventInternal("onInputViewLayoutChanged", runnable, arguments);
+ }
+ }
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
new file mode 100644
index 0000000..4fe9f5a
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents an active Mock IME session, which provides basic primitives to write end-to-end tests
+ * for IME APIs.
+ *
+ * <p>To use {@link MockIme} via {@link MockImeSession}, you need to </p>
+ * <p>Public methods are not thread-safe.</p>
+ */
+public class MockImeSession implements AutoCloseable {
+ private final String mImeEventActionName =
+ "com.android.cts.mockime.action.IME_EVENT." + SystemClock.elapsedRealtimeNanos();
+
+ /** Setting file name to store initialization settings for {@link MockIme}. */
+ static final String MOCK_IME_SETTINGS_FILE = "mockimesettings.data";
+
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10);
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final UiAutomation mUiAutomation;
+
+ private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
+
+ private final static class EventStore {
+ private final static int INITIAL_ARRAY_SIZE = 32;
+
+ @NonNull
+ public final ImeEvent[] mArray;
+ public int mLength;
+
+ public EventStore() {
+ mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
+ mLength = 0;
+ }
+
+ public EventStore(EventStore src, int newLength) {
+ mArray = new ImeEvent[newLength];
+ mLength = src.mLength;
+ System.arraycopy(src.mArray, 0, mArray, 0, src.mLength);
+ }
+
+ public EventStore add(ImeEvent event) {
+ if (mLength + 1 <= mArray.length) {
+ mArray[mLength] = event;
+ ++mLength;
+ return this;
+ } else {
+ return new EventStore(this, mLength * 2).add(event);
+ }
+ }
+
+ public ImeEventStream.ImeEventArray takeSnapshot() {
+ return new ImeEventStream.ImeEventArray(mArray, mLength);
+ }
+ }
+
+ private static final class MockImeEventReceiver extends BroadcastReceiver {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ @NonNull
+ private EventStore mCurrentEventStore = new EventStore();
+
+ @NonNull
+ private final String mActionName;
+
+ public MockImeEventReceiver(@NonNull String actionName) {
+ mActionName = actionName;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TextUtils.equals(mActionName, intent.getAction())) {
+ synchronized (mLock) {
+ mCurrentEventStore =
+ mCurrentEventStore.add(ImeEvent.fromBundle(intent.getExtras()));
+ }
+ }
+ }
+
+ public ImeEventStream.ImeEventArray takeEventSnapshot() {
+ synchronized (mLock) {
+ return mCurrentEventStore.takeSnapshot();
+ }
+ }
+ }
+ private final MockImeEventReceiver mEventReceiver =
+ new MockImeEventReceiver(mImeEventActionName);
+
+ private final ImeEventStream mEventStream =
+ new ImeEventStream(mEventReceiver::takeEventSnapshot);
+
+ private static String executeShellCommand(
+ @NonNull UiAutomation uiAutomation, @NonNull String command) throws IOException {
+ try (ParcelFileDescriptor.AutoCloseInputStream in =
+ new ParcelFileDescriptor.AutoCloseInputStream(
+ uiAutomation.executeShellCommand(command))) {
+ final StringBuilder sb = new StringBuilder();
+ final byte[] buffer = new byte[4096];
+ while (true) {
+ final int numRead = in.read(buffer);
+ if (numRead <= 0) {
+ break;
+ }
+ sb.append(new String(buffer, 0, numRead));
+ }
+ return sb.toString();
+ }
+ }
+
+ @Nullable
+ private String getCurrentInputMethodId() {
+ // TODO: Replace this with IMM#getCurrentInputMethodIdForTesting()
+ return Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ }
+
+ @Nullable
+ private static void writeMockImeSettings(@NonNull Context context,
+ @NonNull String imeEventActionName,
+ @Nullable ImeSettings.Builder imeSettings) throws Exception {
+ context.deleteFile(MOCK_IME_SETTINGS_FILE);
+ try (OutputStream os = context.openFileOutput(MOCK_IME_SETTINGS_FILE, MODE_PRIVATE)) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ ImeSettings.writeToParcel(parcel, imeEventActionName, imeSettings);
+ os.write(parcel.marshall());
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ os.flush();
+ }
+ }
+
+ private ComponentName getMockImeComponentName() {
+ return MockIme.getComponentName(mContext.getPackageName());
+ }
+
+ private String getMockImeId() {
+ return MockIme.getImeId(mContext.getPackageName());
+ }
+
+ private MockImeSession(@NonNull Context context, @NonNull UiAutomation uiAutomation) {
+ mContext = context;
+ mUiAutomation = uiAutomation;
+ }
+
+ private void initialize(@Nullable ImeSettings.Builder imeSettings) throws Exception {
+ // Make sure that MockIME is not selected.
+ if (mContext.getSystemService(InputMethodManager.class)
+ .getInputMethodList()
+ .stream()
+ .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
+ executeShellCommand(mUiAutomation, "ime reset");
+ }
+ if (mContext.getSystemService(InputMethodManager.class)
+ .getEnabledInputMethodList()
+ .stream()
+ .anyMatch(info -> getMockImeComponentName().equals(info.getComponent()))) {
+ throw new IllegalStateException();
+ }
+
+ writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
+
+ mHandlerThread.start();
+ mContext.registerReceiver(mEventReceiver,
+ new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+ new Handler(mHandlerThread.getLooper()));
+
+ executeShellCommand(mUiAutomation, "ime enable " + getMockImeId());
+ executeShellCommand(mUiAutomation, "ime set " + getMockImeId());
+
+ PollingCheck.check("Make sure that MockIME becomes available", TIMEOUT,
+ () -> getMockImeId().equals(getCurrentInputMethodId()));
+ }
+
+ /**
+ * Creates a new Mock IME session. During this session, you can receive various events from
+ * {@link MockIme}.
+ *
+ * @param context {@link Context} to be used to receive inter-process events from the
+ * {@link MockIme} (e.g. via {@link BroadcastReceiver}
+ * @param uiAutomation {@link UiAutomation} object to change the device state that are typically
+ * guarded by permissions.
+ * @param imeSettings Key-value pairs to be passed to the {@link MockIme}.
+ * @return A session object, with which you can retrieve event logs from the {@link MockIme} and
+ * can clean up the session.
+ */
+ @NonNull
+ public static MockImeSession create(
+ @NonNull Context context,
+ @NonNull UiAutomation uiAutomation,
+ @Nullable ImeSettings.Builder imeSettings) throws Exception {
+ final MockImeSession client = new MockImeSession(context, uiAutomation);
+ client.initialize(imeSettings);
+ return client;
+ }
+
+ /**
+ * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
+ * session is created.
+ */
+ public ImeEventStream openEventStream() {
+ return mEventStream.copy();
+ }
+
+ /**
+ * Closes the active session and de-selects {@link MockIme}. Currently which IME will be
+ * selected next is up to the system.
+ */
+ public void close() throws Exception {
+ executeShellCommand(mUiAutomation, "ime reset");
+
+ PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
+ mContext.getSystemService(InputMethodManager.class)
+ .getEnabledInputMethodList()
+ .stream()
+ .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
+
+ mContext.unregisterReceiver(mEventReceiver);
+ mHandlerThread.quitSafely();
+ mContext.deleteFile(MOCK_IME_SETTINGS_FILE);
+ }
+
+ /**
+ * Lets {@link MockIme} to call
+ * {@link android.view.inputmethod.InputConnection#commitText(CharSequence, int)} with the given
+ * parameters.
+ *
+ * <p>This triggers {@code getCurrentInputConnection().commitText(text, newCursorPosition)}.</p>
+ *
+ * @param text to be passed as the {@code text} parameter
+ * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callCommitText(@NonNull CharSequence text, int newCursorPosition) {
+ final Bundle params = new Bundle();
+ params.putCharSequence("text", text);
+ params.putInt("newCursorPosition", newCursorPosition);
+ final ImeCommand command = new ImeCommand(
+ "commitText", SystemClock.elapsedRealtimeNanos(), true, params);
+ final Intent intent = new Intent();
+ intent.setPackage(mContext.getPackageName());
+ intent.setAction(MockIme.getCommandActionName(mImeEventActionName));
+ intent.putExtras(command.toBundle());
+ mContext.sendBroadcast(intent);
+ return command;
+ }
+}
diff --git a/tests/inputmethod/res/xml/method.xml b/tests/inputmethod/res/xml/method.xml
new file mode 100644
index 0000000..2266fba
--- /dev/null
+++ b/tests/inputmethod/res/xml/method.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+</input-method>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index d5ace2e..d3efc72 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -493,8 +493,8 @@
public void testCloseConnection() {
final CharSequence source = "0123456789";
mConnection.commitText(source, source.length());
+ mConnection.setComposingRegion(2, 5);
final Editable text = mConnection.getEditable();
- BaseInputConnection.setComposingSpans(text, 2, 5);
assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
index 41e3efa..9b03ed7 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
@@ -18,9 +18,14 @@
import static org.junit.Assert.assertEquals;
+import android.graphics.Typeface;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.StyleSpan;
import android.view.inputmethod.ExtractedText;
import org.junit.Test;
@@ -38,6 +43,9 @@
extractedText.startOffset = 1;
CharSequence text = "test";
extractedText.text = text;
+ SpannableStringBuilder hint = new SpannableStringBuilder("hint");
+ hint.setSpan(new StyleSpan(Typeface.BOLD), 1, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ extractedText.hint = hint;
Parcel p = Parcel.obtain();
extractedText.writeToParcel(p, 0);
p.setDataPosition(0);
@@ -49,6 +57,9 @@
assertEquals(extractedText.partialStartOffset, target.partialStartOffset);
assertEquals(extractedText.partialEndOffset, target.partialEndOffset);
assertEquals(extractedText.text.toString(), target.text.toString());
+ assertEquals(extractedText.hint.toString(), target.hint.toString());
+ final Spannable hintText = (Spannable) extractedText.hint;
+ assertEquals(1, hintText.getSpans(0, hintText.length(), StyleSpan.class).length);
assertEquals(0, extractedText.describeContents());
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
new file mode 100644
index 0000000..2e6267f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertFalse;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.view.inputmethod.cts.util.TestUtils;
+import android.view.inputmethod.cts.util.WindowFocusStealer;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FocusHandlingTest extends EndToEndImeTestBase {
+ static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+ private final static String TEST_MARKER = "android.view.inputmethod.cts.FocusHandlingTest";
+
+ public EditText launchTestActivity() {
+ final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+ TestActivity.startSync((TestActivity activity) -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText editText = new EditText(activity);
+ editText.setPrivateImeOptions(TEST_MARKER);
+ editText.setHint("editText");
+ editTextRef.set(editText);
+
+ layout.addView(editText);
+ return layout;
+ });
+ return editTextRef.get();
+ }
+
+ @Test
+ public void testOnStartInputCalledOnceIme() throws Exception {
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final EditText editText = launchTestActivity();
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Emulate tap event
+ CtsTouchUtils.emulateTapOnViewCenter(
+ InstrumentationRegistry.getInstrumentation(), editText);
+
+ // Wait until "onStartInput" gets called for the EditText.
+ final ImeEvent onStart = expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInput", event.getEventName())) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+ }, TIMEOUT);
+ assertFalse(stream.dump(), onStart.getEnterState().hasDummyInputConnection());
+ assertFalse(stream.dump(), onStart.getArguments().getBoolean("restarting"));
+
+ // There shouldn't be onStartInput any more.
+ notExpectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+ NOT_EXPECT_TIMEOUT);
+ }
+ }
+
+ @Test
+ public void testSoftInputStateAlwaysVisibleWithoutFocusedEditorView() throws Exception {
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final TestActivity testActivity = TestActivity.startSync((TestActivity activity) -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final TextView textView = new TextView(activity) {
+ @Override
+ public boolean onCheckIsTextEditor() {
+ return false;
+ }
+ };
+ textView.setText("textView");
+ textView.requestFocus();
+
+ activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ layout.addView(textView);
+ return layout;
+ });
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ if (testActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ // There shouldn't be onStartInput because the focused view is not an editor.
+ notExpectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+ TIMEOUT);
+ } else {
+ // For apps that target pre-P devices, onStartInput() should be called.
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+ }
+ }
+ }
+
+ @Test
+ public void testSoftInputStateAlwaysVisibleFocusedEditorView() throws Exception {
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ TestActivity.startSync((TestActivity activity) -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText editText = new EditText(activity);
+ editText.setText("editText");
+ editText.requestFocus();
+
+ activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ layout.addView(editText);
+ return layout;
+ });
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+ }
+ }
+
+ /**
+ * Makes sure that an existing {@link android.view.inputmethod.InputConnection} will not be
+ * invalidated by showing a focusable {@link PopupWindow} with
+ * {@link PopupWindow#INPUT_METHOD_NOT_NEEDED}.
+ *
+ * <p>If {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} is set and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} is not set to a
+ * {@link android.view.Window}, showing that window must not invalidate an existing valid
+ * {@link android.view.inputmethod.InputConnection}.</p>
+ *
+ * @see android.view.WindowManager.LayoutParams#mayUseInputMethod(int)
+ */
+ @Test
+ public void testFocusableWindowDoesNotInvalidateExistingInputConnection() throws Exception {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ instrumentation.getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final EditText editText = launchTestActivity();
+ instrumentation.runOnMainSync(() -> editText.requestFocus());
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInput", event.getEventName())) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+ }, TIMEOUT);
+
+ // Make sure that InputConnection#commitText() works.
+ final ImeCommand commit1 = imeSession.callCommitText("test commit", 1);
+ expectCommand(stream, commit1, TIMEOUT);
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(editText.getText(), "test commit"), TIMEOUT);
+ instrumentation.runOnMainSync(() -> editText.setText(""));
+
+ // Create a popup window that cannot be the IME target.
+ final PopupWindow popupWindow = TestUtils.getOnMainSync(() -> {
+ final Context context = instrumentation.getTargetContext();
+ final PopupWindow popup = new PopupWindow(context);
+ popup.setFocusable(true);
+ popup.setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
+ final TextView textView = new TextView(context);
+ textView.setText("Test Text");
+ popup.setContentView(textView);
+ return popup;
+ });
+
+ // Show the popup window.
+ instrumentation.runOnMainSync(() -> popupWindow.showAsDropDown(editText));
+ instrumentation.waitForIdleSync();
+
+ // Make sure that the EditText no longer has window-focus
+ TestUtils.waitOnMainUntil(() -> !editText.hasWindowFocus(), TIMEOUT);
+
+ // Make sure that InputConnection#commitText() works.
+ final ImeCommand commit2 = imeSession.callCommitText("Hello!", 1);
+ expectCommand(stream, commit2, TIMEOUT);
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(editText.getText(), "Hello!"), TIMEOUT);
+ instrumentation.runOnMainSync(() -> editText.setText(""));
+
+ stream.skipAll();
+
+ // Call InputMethodManager#restartInput()
+ instrumentation.runOnMainSync(() -> {
+ editText.getContext()
+ .getSystemService(InputMethodManager.class)
+ .restartInput(editText);
+ });
+
+ // Make sure that onStartInput() is called with restarting == true.
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInput", event.getEventName())) {
+ return false;
+ }
+ if (!event.getArguments().getBoolean("restarting")) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+ }, TIMEOUT);
+
+ // Make sure that InputConnection#commitText() works.
+ final ImeCommand commit3 = imeSession.callCommitText("World!", 1);
+ expectCommand(stream, commit3, TIMEOUT);
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(editText.getText(), "World!"), TIMEOUT);
+ instrumentation.runOnMainSync(() -> editText.setText(""));
+
+ // Dismiss the popup window.
+ instrumentation.runOnMainSync(() -> popupWindow.dismiss());
+ instrumentation.waitForIdleSync();
+
+ // Make sure that the EditText now has window-focus again.
+ TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+
+ // Make sure that InputConnection#commitText() works.
+ final ImeCommand commit4 = imeSession.callCommitText("Done!", 1);
+ expectCommand(stream, commit4, TIMEOUT);
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(editText.getText(), "Done!"), TIMEOUT);
+ instrumentation.runOnMainSync(() -> editText.setText(""));
+ }
+ }
+
+ /**
+ * Test case for Bug 70629102.
+ *
+ * {@link InputMethodManager#restartInput(View)} can be called even when another process
+ * temporarily owns focused window. {@link InputMethodManager} should continue to work after
+ * the IME target application gains window focus again.
+ */
+ @Test
+ public void testRestartInputWhileOtherProcessHasWindowFocus() throws Exception {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ instrumentation.getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final EditText editText = launchTestActivity();
+ instrumentation.runOnMainSync(() -> editText.requestFocus());
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInput", event.getEventName())) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+ }, TIMEOUT);
+
+ // Get app window token
+ final IBinder appWindowToken = TestUtils.getOnMainSync(
+ () -> editText.getApplicationWindowToken());
+
+ try (WindowFocusStealer focusStealer =
+ WindowFocusStealer.connect(instrumentation.getTargetContext(), TIMEOUT)) {
+
+ focusStealer.stealWindowFocus(appWindowToken, TIMEOUT);
+
+ // Wait until the edit text loses window focus.
+ TestUtils.waitOnMainUntil(() -> !editText.hasWindowFocus(), TIMEOUT);
+
+ // Call InputMethodManager#restartInput()
+ instrumentation.runOnMainSync(() -> {
+ editText.getContext()
+ .getSystemService(InputMethodManager.class)
+ .restartInput(editText);
+ });
+ }
+
+ // Wait until the edit text gains window focus again.
+ TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+
+ // Make sure that InputConnection#commitText() still works.
+ final ImeCommand command = imeSession.callCommitText("test commit", 1);
+ expectCommand(stream, command, TIMEOUT);
+
+ TestUtils.waitOnMainUntil(
+ () -> TextUtils.equals(editText.getText(), "test commit"), TIMEOUT);
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 2b5d7ed..b538d41 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -250,7 +250,7 @@
ApplicationInfo.FLAG_SYSTEM) {
continue;
}
- if (serviceInfo.encryptionAware) {
+ if (serviceInfo.directBootAware) {
hasEncryptionAwareInputMethod = true;
break;
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
new file mode 100644
index 0000000..a3c41bb
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.inputmethod.cts.util.LightNavigationBarVerifier.expectLightNavigationBarNotSupported;
+import static android.view.inputmethod.cts.util.NavigationBarColorVerifier.expectNavigationBarColorNotSupported;
+import static android.view.inputmethod.cts.util.NavigationBarColorVerifier.expectNavigationBarColorSupported;
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.UiAutomation;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Process;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.ImeAwareEditText;
+import android.view.inputmethod.cts.util.NavigationBarInfo;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class NavigationBarColorTest extends EndToEndImeTestBase {
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
+
+ private final static String TEST_MARKER = "android.view.inputmethod.cts.NavigationBarColorTest";
+
+ private static void updateSystemUiVisibility(@NonNull View view, int flags, int mask) {
+ final int currentFlags = view.getSystemUiVisibility();
+ final int newFlags = (currentFlags & ~mask) | (flags & mask);
+ if (currentFlags != newFlags) {
+ view.setSystemUiVisibility(newFlags);
+ }
+ }
+
+ @BeforeClass
+ public static void checkNavigationBar() throws Exception {
+ assumeTrue("This test does not make sense if there is no navigation bar",
+ NavigationBarInfo.getInstance().hasBottomNavigationBar());
+
+ assumeTrue("This test does not make sense if custom navigation bar color is not supported"
+ + " even for typical Activity",
+ NavigationBarInfo.getInstance().supportsNavigationBarColor());
+ }
+
+ /**
+ * Represents test scenarios regarding how a {@link android.view.Window} that has
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} interacts with a different
+ * {@link android.view.Window} that has
+ * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR}.
+ */
+ private enum DimmingTestMode {
+ /**
+ * No {@link AlertDialog} is shown when testing.
+ */
+ NO_DIMMING_DIALOG,
+ /**
+ * An {@link AlertDialog} that has dimming effect is shown above the IME window.
+ */
+ DIMMING_DIALOG_ABOVE_IME,
+ /**
+ * An {@link AlertDialog} that has dimming effect is shown behind the IME window.
+ */
+ DIMMING_DIALOG_BEHIND_IME,
+ }
+
+ @NonNull
+ public TestActivity launchTestActivity(@ColorInt int navigationBarColor,
+ boolean lightNavigationBar, @NonNull DimmingTestMode dimmingTestMode) {
+ return TestActivity.startSync(activity -> {
+ final View contentView;
+ switch (dimmingTestMode) {
+ case NO_DIMMING_DIALOG:
+ case DIMMING_DIALOG_ABOVE_IME: {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ final ImeAwareEditText editText = new ImeAwareEditText(activity);
+ editText.setPrivateImeOptions(TEST_MARKER);
+ editText.setHint("editText");
+ editText.requestFocus();
+ editText.scheduleShowSoftInput();
+ layout.addView(editText);
+ contentView = layout;
+ break;
+ }
+ case DIMMING_DIALOG_BEHIND_IME: {
+ final View view = new View(activity);
+ view.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ contentView = view;
+ break;
+ }
+ default:
+ throw new IllegalStateException("unknown mode=" + dimmingTestMode);
+ }
+ activity.getWindow().setNavigationBarColor(navigationBarColor);
+ updateSystemUiVisibility(contentView,
+ lightNavigationBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0,
+ SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ return contentView;
+ });
+ }
+
+ private AutoCloseable showDialogIfNecessary(
+ @NonNull Activity activity, @NonNull DimmingTestMode dimmingTestMode) {
+ switch (dimmingTestMode) {
+ case NO_DIMMING_DIALOG:
+ // Dialog is not necessary.
+ return () -> {};
+ case DIMMING_DIALOG_ABOVE_IME: {
+ final AlertDialog alertDialog = getOnMainSync(() -> {
+ final TextView textView = new TextView(activity);
+ textView.setText("Dummy");
+ textView.requestFocus();
+ final AlertDialog dialog = new AlertDialog.Builder(activity)
+ .setView(textView)
+ .create();
+ dialog.getWindow().setFlags(FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM,
+ FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+ dialog.show();
+ return dialog;
+ });
+ // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
+ // the UI thread.
+ return () -> alertDialog.dismiss();
+ }
+ case DIMMING_DIALOG_BEHIND_IME: {
+ final AlertDialog alertDialog = getOnMainSync(() -> {
+ final ImeAwareEditText editText = new ImeAwareEditText(activity);
+ editText.setPrivateImeOptions(TEST_MARKER);
+ editText.setHint("editText");
+ editText.requestFocus();
+ editText.scheduleShowSoftInput();
+ final AlertDialog dialog = new AlertDialog.Builder(activity)
+ .setView(editText)
+ .create();
+ dialog.getWindow().setFlags(FLAG_DIM_BEHIND,
+ FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+ dialog.show();
+ return dialog;
+ });
+ // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
+ // the UI thread.
+ return () -> alertDialog.dismiss();
+ }
+ default:
+ throw new IllegalStateException("unknown mode=" + dimmingTestMode);
+ }
+ }
+
+ @NonNull
+ private ImeSettings.Builder imeSettingForSolidNavigationBar(@ColorInt int navigationBarColor,
+ boolean lightNavigationBar) {
+ final ImeSettings.Builder builder = new ImeSettings.Builder();
+ builder.setNavigationBarColor(navigationBarColor);
+ if (lightNavigationBar) {
+ builder.setInputViewSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ }
+ return builder;
+ }
+
+ @NonNull
+ private ImeSettings.Builder imeSettingForFloatingIme(@ColorInt int navigationBarColor,
+ boolean lightNavigationBar) {
+ final ImeSettings.Builder builder = new ImeSettings.Builder();
+ builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ // As documented, Window#setNavigationBarColor() is actually ignored when the IME window
+ // does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS. We are calling setNavigationBarColor()
+ // to ensure it.
+ builder.setNavigationBarColor(navigationBarColor);
+ if (lightNavigationBar) {
+ // Although the document says that Window#setNavigationBarColor() requires
+ // SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR to work, currently it's not true for IME windows.
+ // TODO: Fix this anomaly
+ builder.setInputViewSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ }
+ return builder;
+ }
+
+ @NonNull
+ private Bitmap getNavigationBarBitmap(@NonNull ImeSettings.Builder builder,
+ @ColorInt int appNavigationBarColor, boolean appLightNavigationBar,
+ int navigationBarHeight, @NonNull DimmingTestMode dimmingTestMode)
+ throws Exception {
+ final UiAutomation uiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(), uiAutomation, builder)) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final TestActivity activity = launchTestActivity(
+ appNavigationBarColor, appLightNavigationBar, dimmingTestMode);
+
+ // Show AlertDialog if necessary, based on the dimming test mode.
+ try (AutoCloseable dialogCloser = showDialogIfNecessary(
+ activity, dimmingTestMode)) {
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Wait until "onStartInput" gets called for the EditText.
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInputView", event.getEventName())) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+ }, TIMEOUT);
+
+ // Wait until MockIme's layout becomes stable.
+ final ImeLayoutInfo lastLayout =
+ waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+ assertNotNull(lastLayout);
+
+ final Bitmap bitmap = uiAutomation.takeScreenshot();
+ return Bitmap.createBitmap(bitmap, 0, bitmap.getHeight() - navigationBarHeight,
+ bitmap.getWidth(), navigationBarHeight);
+ }
+ }
+ }
+
+ @Test
+ public void testSetNavigationBarColor() throws Exception {
+ final NavigationBarInfo info = NavigationBarInfo.getInstance();
+
+ // Make sure that Window#setNavigationBarColor() works for IMEs.
+ expectNavigationBarColorSupported(color ->
+ getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, false),
+ Color.BLACK, false, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.NO_DIMMING_DIALOG));
+
+ // Make sure that IME's navigation bar can be transparent
+ expectNavigationBarColorSupported(color ->
+ getNavigationBarBitmap(imeSettingForSolidNavigationBar(Color.TRANSPARENT, false),
+ color, false, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.NO_DIMMING_DIALOG));
+
+ // Make sure that Window#setNavigationBarColor() is ignored when
+ // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is unset
+ expectNavigationBarColorNotSupported(color ->
+ getNavigationBarBitmap(imeSettingForFloatingIme(color, false),
+ Color.BLACK, false, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.NO_DIMMING_DIALOG));
+ }
+
+ @Test
+ public void testLightNavigationBar() throws Exception {
+ final NavigationBarInfo info = NavigationBarInfo.getInstance();
+
+ assumeTrue("This test does not make sense if light navigation bar is not supported"
+ + " even for typical Activity", info.supportsLightNavigationBar());
+
+ // Currently SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR is ignored for IME windows.
+ // TODO: Support SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR for IME windows (Bug 69002467)
+ expectLightNavigationBarNotSupported((color, lightMode) ->
+ getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
+ Color.BLACK, false, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.NO_DIMMING_DIALOG));
+
+ // Currently there is no way for IMEs to opt-out dark/light navigation bar mode.
+ // TODO: Allows IMEs to opt out dark/light navigation bar mode (Bug 69111208).
+ expectLightNavigationBarNotSupported((color, lightMode) ->
+ getNavigationBarBitmap(imeSettingForFloatingIme(Color.BLACK, false),
+ color, lightMode, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.NO_DIMMING_DIALOG));
+ }
+
+ @Test
+ public void testDimmingWindow() throws Exception {
+ final NavigationBarInfo info = NavigationBarInfo.getInstance();
+
+ assumeTrue("This test does not make sense if dimming windows do not affect light "
+ + " light navigation bar for typical Activities",
+ info.supportsDimmingWindowLightNavigationBarOverride());
+
+ // Currently SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR is ignored for IME windows.
+ // TODO: Support SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR for IME windows (Bug 69002467)
+ expectLightNavigationBarNotSupported((color, lightMode) ->
+ getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
+ Color.BLACK, false, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.DIMMING_DIALOG_BEHIND_IME));
+
+ // If a dimming window is shown above the IME window, IME window's
+ // SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR should be canceled.
+ expectLightNavigationBarNotSupported((color, lightMode) ->
+ getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
+ Color.BLACK, false, info.getBottomNavigationBerHeight(),
+ DimmingTestMode.DIMMING_DIALOG_ABOVE_IME));
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
new file mode 100644
index 0000000..014ef90
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class OnScreenPositionTest extends EndToEndImeTestBase {
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
+
+ private final static String TEST_MARKER = "android.view.inputmethod.cts.OnScreenPositionTest";
+
+ public EditText launchTestActivity() {
+ final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+ TestActivity.startSync((TestActivity activity) -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText editText = new EditText(activity);
+ editText.setPrivateImeOptions(TEST_MARKER);
+ editText.setHint("editText");
+ editTextRef.set(editText);
+
+ layout.addView(editText);
+ return layout;
+ });
+ return editTextRef.get();
+ }
+
+ /**
+ * Regression test for Bug 33308065.
+ */
+ @Test
+ public void testImeIsNotBehindNavBar() throws Exception {
+ final int EXPECTED_KEYBOARD_HEIGHT = 100;
+
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder()
+ .setInputViewHeightWithoutSystemWindowInset(EXPECTED_KEYBOARD_HEIGHT))) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final EditText editText = launchTestActivity();
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Emulate tap event
+ CtsTouchUtils.emulateTapOnViewCenter(
+ InstrumentationRegistry.getInstrumentation(), editText);
+
+ // Wait until "onStartInput" gets called for the EditText.
+ expectEvent(stream, event -> {
+ if (!TextUtils.equals("onStartInputView", event.getEventName())) {
+ return false;
+ }
+ final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
+ return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
+ }, TIMEOUT);
+
+ // Wait until MockIme's layout becomes stable.
+ final ImeLayoutInfo lastLayout =
+ waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+ assertNotNull(lastLayout);
+
+ // We consider that the screenRectWithoutNavBar is a union of those two rects.
+ // See the following methods for details.
+ // - DecorView#getColorViewTopInset(int, int)
+ // - DecorView#getColorViewBottomInset(int, int)
+ // - DecorView#getColorViewRightInset(int, int)
+ // - DecorView#getColorViewLeftInset(int, int)
+ final Rect screenRectWithoutNavBar = lastLayout.getScreenRectWithoutStableInset();
+ screenRectWithoutNavBar.union(lastLayout.getScreenRectWithoutSystemWindowInset());
+
+ final Rect keyboardViewBounds = lastLayout.getInputViewBoundsInScreen();
+ // By default, the above region must contain the keyboard view region.
+ assertTrue("screenRectWithoutNavBar(" + screenRectWithoutNavBar + ") must"
+ + " contain keyboardViewBounds(" + keyboardViewBounds + ")",
+ screenRectWithoutNavBar.contains(keyboardViewBounds));
+
+ // Make sure that the keyboard height is expected. Here we assume that the expected
+ // height is small enough for all the Android-based devices to show.
+ assertEquals(EXPECTED_KEYBOARD_HEIGHT,
+ lastLayout.getInputViewBoundsInScreen().height());
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
new file mode 100644
index 0000000..1512684
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import android.os.Process;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.InputType;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.SearchView;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SearchViewTest extends EndToEndImeTestBase {
+ static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ public SearchView launchTestActivity() {
+ final AtomicReference<SearchView> searchViewRef = new AtomicReference<>();
+ TestActivity.startSync((TestActivity activity) -> {
+ final LinearLayout layout = new LinearLayout(activity);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ final EditText initialFocusedEditText = new EditText(activity);
+ initialFocusedEditText.setHint("initialFocusedTextView");
+
+ final SearchView searchView = new SearchView(activity);
+ searchViewRef.set(searchView);
+
+ searchView.setQueryHint("hint");
+ searchView.setIconifiedByDefault(false);
+ searchView.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
+ searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
+
+ layout.addView(initialFocusedEditText);
+ layout.addView(searchView);
+ return layout;
+ });
+ return searchViewRef.get();
+ }
+
+ @Test
+ public void testTapThenSetQuery() throws Exception {
+ try(MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+
+ final SearchView searchView = launchTestActivity();
+
+ // Wait until the MockIme gets bound to the TestActivity.
+ expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+ // Emulate tap event
+ CtsTouchUtils.emulateTapOnViewCenter(
+ InstrumentationRegistry.getInstrumentation(), searchView);
+
+ // Wait until "showSoftInput" gets called with a real InputConnection
+ expectEvent(stream, event ->
+ "showSoftInput".equals(event.getEventName())
+ && !event.getExitState().hasDummyInputConnection(), TIMEOUT);
+
+ // Make sure that "setQuery" triggers "hideSoftInput" in the IME side.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> searchView.setQuery("test", true /* submit */));
+ expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), TIMEOUT);
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
new file mode 100644
index 0000000..e68a2c7
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+public class EndToEndImeTestBase {
+
+ @BeforeClass
+ public static void assumeFeatureInputMethod() {
+ assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS));
+ }
+
+ @Before
+ public void showStateInitializeActivity() {
+ final Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(InstrumentationRegistry.getTargetContext(), StateInitializeActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/IWindowFocusStealer.aidl b/tests/inputmethod/src/android/view/inputmethod/cts/util/IWindowFocusStealer.aidl
new file mode 100644
index 0000000..aef3bf3
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/IWindowFocusStealer.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.os.ResultReceiver;
+
+interface IWindowFocusStealer {
+ void stealWindowFocus(in IBinder parentAppWindowToken, in ResultReceiver resultReceiver);
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/ImeAwareEditText.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/ImeAwareEditText.java
new file mode 100644
index 0000000..b48e750
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/ImeAwareEditText.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+public class ImeAwareEditText extends EditText {
+ private boolean mHasPendingShowSoftInputRequest;
+ final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
+
+ public ImeAwareEditText(Context context) {
+ super(context, null);
+ }
+
+ public ImeAwareEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * This method is called back by the system when the system is about to establish a connection
+ * to the current input method.
+ *
+ * <p>This is a good and reliable signal to schedule a pending task to call
+ * {@link InputMethodManager#showSoftInput(View, int)}.</p>
+ *
+ * @param editorInfo context about the text input field.
+ * @return {@link InputConnection} to be passed to the input method.
+ */
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
+ final InputConnection ic = super.onCreateInputConnection(editorInfo);
+ if (mHasPendingShowSoftInputRequest) {
+ removeCallbacks(mRunShowSoftInputIfNecessary);
+ post(mRunShowSoftInputIfNecessary);
+ }
+ return ic;
+ }
+
+ private void showSoftInputIfNecessary() {
+ if (mHasPendingShowSoftInputRequest) {
+ final InputMethodManager imm =
+ getContext().getSystemService(InputMethodManager.class);
+ imm.showSoftInput(this, 0);
+ mHasPendingShowSoftInputRequest = false;
+ }
+ }
+
+ public void scheduleShowSoftInput() {
+ final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
+ if (imm.isActive(this)) {
+ // This means that ImeAwareEditText is already connected to the IME.
+ // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
+ mHasPendingShowSoftInputRequest = false;
+ removeCallbacks(mRunShowSoftInputIfNecessary);
+ imm.showSoftInput(this, 0);
+ return;
+ }
+
+ // Otherwise, InputMethodManager#showSoftInput() should be deferred after
+ // onCreateInputConnection().
+ mHasPendingShowSoftInputRequest = true;
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
new file mode 100644
index 0000000..401caa3
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.SparseIntArray;
+
+import java.util.Arrays;
+import java.util.OptionalDouble;
+import java.util.function.Supplier;
+
+/**
+ * A utility class to evaluate if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is
+ * supported on the device or not.
+ */
+public class LightNavigationBarVerifier {
+
+ /** This value actually does not have strong rationale. */
+ private static float LIGHT_NAVBAR_SUPPORTED_THRESHOLD = 20.0f;
+
+ /** This value actually does not have strong rationale. */
+ private static float LIGHT_NAVBAR_NOT_SUPPORTED_THRESHOLD = 5.0f;
+
+ @FunctionalInterface
+ public interface ScreenshotSupplier {
+ @NonNull
+ Bitmap takeScreenshot(@ColorInt int navigationBarColor, boolean lightMode) throws Exception;
+ }
+
+ public enum ResultType {
+ /**
+ * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seems to be not supported.
+ */
+ NOT_SUPPORTED,
+ /**
+ * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seems to be supported.
+ */
+ SUPPORTED,
+ /**
+ * Not sure if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is supported.
+ */
+ UNKNOWN,
+ }
+
+ static final class Result {
+ @NonNull
+ public final ResultType mResult;
+ @NonNull
+ public final Supplier<String> mAssertionMessageProvider;
+
+ public Result(@NonNull ResultType result,
+ @Nullable Supplier<String> assertionMessageProvider) {
+ mResult = result;
+ mAssertionMessageProvider = assertionMessageProvider;
+ }
+
+ @NonNull
+ public ResultType getResult() {
+ return mResult;
+ }
+
+ @NonNull
+ public String getAssertionMessage() {
+ return mAssertionMessageProvider.get();
+ }
+ }
+
+ /**
+ * Asserts that {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is supported on
+ * this device.
+ *
+ * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+ */
+ public static void expectLightNavigationBarSupported(
+ @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+ final Result result = verify(screenshotSupplier);
+ assertEquals(result.getAssertionMessage(), ResultType.SUPPORTED, result.getResult());
+ }
+
+ /**
+ * Asserts that {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} is not supported
+ * on this device.
+ *
+ * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+ */
+ public static void expectLightNavigationBarNotSupported(
+ @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+ final Result result = verify(screenshotSupplier);
+ assertEquals(result.getAssertionMessage(), ResultType.NOT_SUPPORTED, result.getResult());
+ }
+
+ @FunctionalInterface
+ private interface ColorOperator {
+ int operate(@ColorInt int color1, @ColorInt int color2);
+ }
+
+ private static int[] operateColorArrays(@NonNull int[] pixels1, @NonNull int[] pixels2,
+ @NonNull ColorOperator operator) {
+ assertEquals(pixels1.length, pixels2.length);
+ final int numPixels = pixels1.length;
+ final int[] result = new int[numPixels];
+ for (int i = 0; i < numPixels; ++i) {
+ result[i] = operator.operate(pixels1[i], pixels2[i]);
+ }
+ return result;
+ }
+
+ @NonNull
+ private static int[] getPixels(@NonNull Bitmap bitmap) {
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+ final int[] pixels = new int[width * height];
+ bitmap.getPixels(pixels, 0 /* offset */, width /* stride */, 0 /* x */, 0 /* y */,
+ width, height);
+ return pixels;
+ }
+
+ @NonNull
+ static Result verify(
+ @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+ final int[] darkNavBarPixels = getPixels(
+ screenshotSupplier.takeScreenshot(Color.BLACK, false));
+ final int[] lightNavBarPixels = getPixels(
+ screenshotSupplier.takeScreenshot(Color.BLACK, true));
+
+ if (darkNavBarPixels.length != lightNavBarPixels.length) {
+ throw new IllegalStateException("Pixel count mismatch."
+ + " dark=" + darkNavBarPixels.length + " light=" + lightNavBarPixels.length);
+ }
+
+ final int[][] channelDiffs = new int[][] {
+ operateColorArrays(darkNavBarPixels, lightNavBarPixels,
+ (dark, light) -> Color.red(dark) - Color.red(light)),
+ operateColorArrays(darkNavBarPixels, lightNavBarPixels,
+ (dark, light) -> Color.green(dark) - Color.green(light)),
+ operateColorArrays(darkNavBarPixels, lightNavBarPixels,
+ (dark, light) -> Color.blue(dark) - Color.blue(light)),
+ };
+
+ if (Arrays.stream(channelDiffs).allMatch(
+ diffs -> Arrays.stream(diffs).allMatch(diff -> diff == 0))) {
+ // Exactly the same image. Safe to conclude that light navigation bar is not supported.
+ return new Result(ResultType.NOT_SUPPORTED, () -> dumpDiffStreams(channelDiffs));
+ }
+
+ if (Arrays.stream(channelDiffs).anyMatch(diffs -> {
+ final OptionalDouble average = Arrays.stream(diffs).filter(diff -> diff != 0).average();
+ return average.isPresent() && average.getAsDouble() > LIGHT_NAVBAR_SUPPORTED_THRESHOLD;
+ })) {
+ // If darkNavBarPixels have brighter pixels in at least one color channel
+ // (red, green, blue), we consider that it is because light navigation bar takes effect.
+ // Keep in mind that with SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR navigation bar buttons
+ // are expected to become darker. So if everything works fine, darkNavBarPixels should
+ // have brighter pixels.
+ return new Result(ResultType.SUPPORTED, () -> dumpDiffStreams(channelDiffs));
+ }
+
+ if (Arrays.stream(channelDiffs).allMatch(diffs -> {
+ final OptionalDouble average = Arrays.stream(diffs).filter(diff -> diff != 0).average();
+ return average.isPresent()
+ && Math.abs(average.getAsDouble()) < LIGHT_NAVBAR_NOT_SUPPORTED_THRESHOLD;
+ })) {
+ // If all color channels (red, green, blue) have diffs less than a certain threshold,
+ // consider light navigation bar is not supported. For instance, some devices may
+ // intentionally add some fluctuations to the navigation bar button colors/positions.
+ return new Result(ResultType.NOT_SUPPORTED, () -> dumpDiffStreams(channelDiffs));
+ }
+
+ return new Result(ResultType.UNKNOWN, () -> dumpDiffStreams(channelDiffs));
+ }
+
+ @NonNull
+ private static String dumpDiffStreams(@NonNull int[][] diffStreams) {
+ final String[] channelNames = {"red", "green", "blue"};
+ final StringBuilder sb = new StringBuilder();
+ sb.append("diff histogram: ");
+ for (int i = 0; i < diffStreams.length; ++i) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ final int[] channel = diffStreams[i];
+ final SparseIntArray histogram = new SparseIntArray();
+ Arrays.stream(channel).sorted().forEachOrdered(
+ diff -> histogram.put(diff, histogram.get(diff, 0) + 1));
+ sb.append(channelNames[i]).append(": ").append(histogram);
+ }
+ return sb.toString();
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
new file mode 100644
index 0000000..6cdb000
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarColorVerifier.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+/**
+ * A utility class to evaluate if {@link android.view.Window#setNavigationBarColor(int)} is
+ * supported on the device or not.
+ */
+public class NavigationBarColorVerifier {
+
+ private static final class ScreenShot {
+ @ColorInt
+ public final int mBackgroundColor;
+ @NonNull
+ public final int[] mPixels;
+
+ public ScreenShot(@ColorInt int backgroundColor, @NonNull Bitmap bitmap) {
+ mBackgroundColor = backgroundColor;
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+ mPixels = new int[width * height];
+ bitmap.getPixels(mPixels, 0 /* offset */, width /* stride */, 0 /* x */, 0 /* y */,
+ width, height);
+ }
+ }
+
+ public enum ResultType {
+ /**
+ * {@link android.view.Window#setNavigationBarColor(int)} seems to be not supported.
+ */
+ NOT_SUPPORTED,
+ /**
+ * {@link android.view.Window#setNavigationBarColor(int)} seems to be supported.
+ */
+ SUPPORTED,
+ }
+
+ static final class Result {
+ @NonNull
+ public final ResultType mResult;
+ @NonNull
+ public final String mAssertionMessage;
+
+ public Result(@NonNull ResultType result, @NonNull String assertionMessage) {
+ mResult = result;
+ mAssertionMessage = assertionMessage;
+ }
+
+ @NonNull
+ public ResultType getResult() {
+ return mResult;
+ }
+
+ @NonNull
+ public String getAssertionMessage() {
+ return mAssertionMessage;
+ }
+ }
+
+ @FunctionalInterface
+ public interface ScreenshotSupplier {
+ @NonNull
+ Bitmap takeScreenshot(@ColorInt int navigationBarColor) throws Exception;
+ }
+
+ @NonNull
+ static Result verify(@NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+ final ArrayList<ScreenShot> screenShots = new ArrayList<>();
+ final int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
+ for (int color : colors) {
+ screenShots.add(new ScreenShot(color, screenshotSupplier.takeScreenshot(color)));
+ }
+ return verifyInternal(screenShots);
+ }
+
+ /**
+ * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is supported on this
+ * device.
+ *
+ * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+ */
+ public static void expectNavigationBarColorSupported(
+ @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+ final Result result = verify(screenshotSupplier);
+ assertEquals(result.getAssertionMessage(), ResultType.SUPPORTED, result.getResult());
+ }
+
+ /**
+ * Asserts that {@link android.view.Window#setNavigationBarColor(int)} is not supported on this
+ * device.
+ *
+ * @param screenshotSupplier callback to provide {@link Bitmap} of the navigation bar region
+ */
+ public static void expectNavigationBarColorNotSupported(
+ @NonNull ScreenshotSupplier screenshotSupplier) throws Exception {
+ final Result result = verify(screenshotSupplier);
+ assertEquals(result.getAssertionMessage(), ResultType.NOT_SUPPORTED, result.getResult());
+ }
+
+ private static Result verifyInternal(@NonNull ArrayList<ScreenShot> screenShots) {
+ final int numScreenShots = screenShots.size();
+ assertTrue(
+ "This algorithm requires at least 3 screen shots. size=" + numScreenShots,
+ numScreenShots >= 3);
+ assertEquals("All screenshots must have different background colors",
+ numScreenShots,
+ screenShots.stream()
+ .mapToInt(screenShot -> screenShot.mBackgroundColor)
+ .distinct()
+ .count());
+ assertEquals("All screenshots must have the same pixel count",
+ 1,
+ screenShots.stream()
+ .mapToInt(screenShot -> screenShot.mPixels.length)
+ .distinct()
+ .count());
+
+ long numCompletelyFixedColorPixels = 0;
+ long numColorChangedAsRequestedPixels = 0;
+ final int numPixels = screenShots.get(0).mPixels.length;
+ for (int pixelIndex = 0; pixelIndex < numPixels; ++pixelIndex) {
+ final int i = pixelIndex;
+ final long numFoundColors = screenShots.stream()
+ .mapToInt(screenShot -> screenShot.mPixels[i])
+ .distinct()
+ .count();
+ if (numFoundColors == 1) {
+ numCompletelyFixedColorPixels++;
+ }
+ final long matchingScore = screenShots.stream()
+ .filter(screenShot -> screenShot.mPixels[i] == screenShot.mBackgroundColor)
+ .count();
+ if (matchingScore == numScreenShots) {
+ numColorChangedAsRequestedPixels++;
+ }
+ }
+ final String assertionMessage = "numPixels=" + numPixels
+ + " numColorChangedAsRequestedPixels=" + numColorChangedAsRequestedPixels
+ + " numCompletelyFixedColorPixels=" + numCompletelyFixedColorPixels;
+
+ // OK, even 1 pixel is enough.
+ if (numColorChangedAsRequestedPixels > 0) {
+ return new Result(ResultType.SUPPORTED, assertionMessage);
+ }
+
+ return new Result(ResultType.NOT_SUPPORTED, assertionMessage);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
new file mode 100644
index 0000000..3c71968
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/NavigationBarInfo.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+
+import android.app.AlertDialog;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.util.Size;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.widget.TextView;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A utility class to write tests that depend on some capabilities related to navigation bar.
+ */
+public class NavigationBarInfo {
+ private static final long BEFORE_SCREENSHOT_WAIT = TimeUnit.SECONDS.toMillis(1);
+
+ private final boolean mHasBottomNavigationBar;
+ private final int mBottomNavigationBerHeight;
+ private final boolean mSupportsNavigationBarColor;
+ private final boolean mSupportsLightNavigationBar;
+ private final boolean mSupportsDimmingWindowLightNavigationBarOverride;
+
+ private NavigationBarInfo(boolean hasBottomNavigationBar, int bottomNavigationBerHeight,
+ boolean supportsNavigationBarColor, boolean supportsLightNavigationBar,
+ boolean supportsDimmingWindowLightNavigationBarOverride) {
+ mHasBottomNavigationBar = hasBottomNavigationBar;
+ mBottomNavigationBerHeight = bottomNavigationBerHeight;
+ mSupportsNavigationBarColor = supportsNavigationBarColor;
+ mSupportsLightNavigationBar = supportsLightNavigationBar;
+ mSupportsDimmingWindowLightNavigationBarOverride =
+ supportsDimmingWindowLightNavigationBarOverride;
+ }
+
+ @Nullable
+ private static NavigationBarInfo sInstance;
+
+ /**
+ * Returns a {@link NavigationBarInfo} instance.
+ *
+ * <p>As a performance optimizations, this method internally caches the previous result and
+ * returns the same result if this gets called multiple times.</p>
+ *
+ * <p>Note: The caller should be aware that this method may launch {@link TestActivity}
+ * internally.</p>
+ *
+ * @return {@link NavigationBarInfo} obtained with {@link TestActivity}.
+ */
+ @NonNull
+ public static NavigationBarInfo getInstance() throws Exception {
+ if (sInstance != null) {
+ return sInstance;
+ }
+
+ final int actualBottomInset;
+ {
+ final AtomicReference<View> viewRef = new AtomicReference<>();
+ TestActivity.startSync((TestActivity activity) -> {
+ final View view = new View(activity);
+ view.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ viewRef.set(view);
+ return view;
+ }, Intent.FLAG_ACTIVITY_NO_ANIMATION);
+
+ final View view = viewRef.get();
+
+ final WindowInsets windowInsets = getOnMainSync(() -> view.getRootWindowInsets());
+ if (!windowInsets.hasStableInsets() || windowInsets.getStableInsetBottom() <= 0) {
+ return new NavigationBarInfo(false, 0, false, false, false);
+ }
+ final Size displaySize = getOnMainSync(() -> {
+ final Point size = new Point();
+ view.getDisplay().getRealSize(size);
+ return new Size(size.x, size.y);
+ });
+
+ final Rect viewBoundsOnScreen = getOnMainSync(() -> {
+ final int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+ final int x = xy[0];
+ final int y = xy[1];
+ return new Rect(x, y, x + view.getWidth(), y + view.getHeight());
+ });
+ actualBottomInset = displaySize.getHeight() - viewBoundsOnScreen.bottom;
+ if (actualBottomInset != windowInsets.getStableInsetBottom()) {
+ sInstance = new NavigationBarInfo(false, 0, false, false, false);
+ return sInstance;
+ }
+ }
+
+ final boolean colorSupported = NavigationBarColorVerifier.verify(
+ color -> getBottomNavigationBarBitmapForActivity(
+ color, false /* lightNavigationBar */, actualBottomInset,
+ false /* showDimmingDialog */)).getResult()
+ == NavigationBarColorVerifier.ResultType.SUPPORTED;
+
+ final boolean lightModeSupported = LightNavigationBarVerifier.verify(
+ (color, lightNavigationBar) -> getBottomNavigationBarBitmapForActivity(
+ color, lightNavigationBar, actualBottomInset,
+ false /* showDimmingDialog */)).getResult()
+ == LightNavigationBarVerifier.ResultType.SUPPORTED;
+
+ final boolean dimmingSupported = lightModeSupported && LightNavigationBarVerifier.verify(
+ (color, lightNavigationBar) -> getBottomNavigationBarBitmapForActivity(
+ color, lightNavigationBar, actualBottomInset,
+ true /* showDimmingDialog */)).getResult()
+ == LightNavigationBarVerifier.ResultType.NOT_SUPPORTED;
+
+ sInstance = new NavigationBarInfo(
+ true, actualBottomInset, colorSupported, lightModeSupported, dimmingSupported);
+ return sInstance;
+ }
+
+ @NonNull
+ private static Bitmap getBottomNavigationBarBitmapForActivity(
+ @ColorInt int navigationBarColor, boolean lightNavigationBar,
+ int bottomNavigationBarHeight, boolean showDimmingDialog) throws Exception {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+
+ final TestActivity testActivity = TestActivity.startSync(activity -> {
+ final View view = new View(activity);
+ activity.getWindow().setNavigationBarColor(navigationBarColor);
+
+ // Set/unset SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR if necessary.
+ final int currentVis = view.getSystemUiVisibility();
+ final int newVis = (currentVis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
+ | (lightNavigationBar ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0);
+ if (currentVis != newVis) {
+ view.setSystemUiVisibility(newVis);
+ }
+
+ view.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ return view;
+ });
+ instrumentation.waitForIdleSync();
+
+ final AlertDialog dialog;
+ if (showDimmingDialog) {
+ dialog = getOnMainSync(() -> {
+ final TextView textView = new TextView(testActivity);
+ textView.setText("Dimming Window");
+ final AlertDialog alertDialog = new AlertDialog.Builder(testActivity)
+ .setView(textView)
+ .create();
+ alertDialog.getWindow().setFlags(FLAG_DIM_BEHIND, FLAG_DIM_BEHIND);
+ alertDialog.show();
+ return alertDialog;
+ });
+ } else {
+ dialog = null;
+ }
+
+ Thread.sleep(BEFORE_SCREENSHOT_WAIT);
+
+ final Bitmap fullBitmap = getInstrumentation().getUiAutomation().takeScreenshot();
+ final Bitmap bottomNavBarBitmap = Bitmap.createBitmap(fullBitmap, 0,
+ fullBitmap.getHeight() - bottomNavigationBarHeight, fullBitmap.getWidth(),
+ bottomNavigationBarHeight);
+ if (dialog != null) {
+ // Dialog#dismiss() is a thread safe method so we don't need to call this from the UI
+ // thread.
+ dialog.dismiss();
+ }
+ return bottomNavBarBitmap;
+ }
+
+ /**
+ * @return {@code true} if this device seems to have bottom navigation bar.
+ */
+ public boolean hasBottomNavigationBar() {
+ return mHasBottomNavigationBar;
+ }
+
+ /**
+ * @return height of the bottom navigation bar. Valid only when
+ * {@link #hasBottomNavigationBar()} returns {@code true}
+ */
+ public int getBottomNavigationBerHeight() {
+ return mBottomNavigationBerHeight;
+ }
+
+ /**
+ * @return {@code true} if {@link android.view.Window#setNavigationBarColor(int)} seem to take
+ * effect on this device. Valid only when {@link #hasBottomNavigationBar()} returns
+ * {@code true}
+ */
+ public boolean supportsNavigationBarColor() {
+ return mSupportsNavigationBarColor;
+ }
+
+ /**
+ * @return {@code true} if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seem to
+ * take effect on this device. Valid only when {@link #hasBottomNavigationBar()} returns
+ * {@code true}
+ */
+ public boolean supportsLightNavigationBar() {
+ return mSupportsLightNavigationBar;
+ }
+
+ /**
+ * @return {@code true} if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} will be
+ * canceled when a {@link android.view.Window} with
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} is shown.
+ */
+ public boolean supportsDimmingWindowLightNavigationBarOverride() {
+ return mSupportsDimmingWindowLightNavigationBarOverride;
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/StateInitializeActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/StateInitializeActivity.java
new file mode 100644
index 0000000..e9c15cc
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/StateInitializeActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A nop {@link Activity} to make sure that the test starts from a deterministic state.
+ *
+ * <p>Currently this {@link Activity} makes sure the following things</p>
+ * <li>
+ * <ul>Hide the software keyboard with
+ * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul>
+ * <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}.
+ * </ul>
+ * </li>
+ */
+public class StateInitializeActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle bundle){
+ super.onCreate(bundle);
+ final View view = new View(this);
+ view.setBackgroundColor(Color.WHITE);
+ view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // Make sure that IME is hidden in the initial state
+ getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ // Make sure that navigation bar is rendered with black (if supported).
+ getWindow().setNavigationBarColor(Color.BLACK);
+
+ setContentView(view);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
new file mode 100644
index 0000000..85c3e66
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+public final class TestActivity extends Activity {
+
+ private static final AtomicReference<Function<TestActivity, View>> sInitializer =
+ new AtomicReference<>();
+
+ private Function<TestActivity, View> mInitializer = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (mInitializer == null) {
+ mInitializer = sInitializer.get();
+ }
+ setContentView(mInitializer.apply(this));
+ }
+
+ /**
+ * Launches {@link TestActivity} with the given initialization logic for content view.
+ *
+ * <p>As long as you are using {@link android.support.test.runner.AndroidJUnitRunner}, the test
+ * runner automatically calls {@link Activity#finish()} for the {@link Activity} launched when
+ * the test finished. You do not need to explicitly call {@link Activity#finish()}.</p>
+ *
+ * @param activityInitializer initializer to supply {@link View} to be passed to
+ * {@link Activity#setContentView(View)}
+ * @return {@link TestActivity} launched
+ */
+ public static TestActivity startSync(
+ @NonNull Function<TestActivity, View> activityInitializer) {
+ return startSync(activityInitializer, 0 /* noAnimation */);
+ }
+
+ /**
+ * Launches {@link TestActivity} with the given initialization logic for content view.
+ *
+ * <p>As long as you are using {@link android.support.test.runner.AndroidJUnitRunner}, the test
+ * runner automatically calls {@link Activity#finish()} for the {@link Activity} launched when
+ * the test finished. You do not need to explicitly call {@link Activity#finish()}.</p>
+ *
+ * @param activityInitializer initializer to supply {@link View} to be passed to
+ * {@link Activity#setContentView(View)}
+ * @param additionalFlags flags to be set to {@link Intent#setFlags(int)}
+ * @return {@link TestActivity} launched
+ */
+ public static TestActivity startSync(
+ @NonNull Function<TestActivity, View> activityInitializer,
+ int additionalFlags) {
+ sInitializer.set(activityInitializer);
+ final Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(InstrumentationRegistry.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .addFlags(additionalFlags);
+ return (TestActivity) InstrumentationRegistry
+ .getInstrumentation().startActivitySync(intent);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
new file mode 100644
index 0000000..b5f9c9a
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.app.Instrumentation;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+
+public final class TestUtils {
+ private static final long TIME_SLICE = 100; // msec
+
+ /**
+ * Retrieves a value that needs to be obtained on the main thread.
+ *
+ * <p>A simple utility method that helps to return an object from the UI thread.</p>
+ *
+ * @param supplier callback to be called on the UI thread to return a value
+ * @param <T> Type of the value to be returned
+ * @return Value returned from {@code supplier}
+ */
+ public static <T> T getOnMainSync(@NonNull Supplier<T> supplier) {
+ final AtomicReference<T> result = new AtomicReference<>();
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.runOnMainSync(() -> result.set(supplier.get()));
+ return result.get();
+ }
+
+ /**
+ * Does polling loop on the UI thread to wait until the given condition is met.
+ *
+ * @param condition Condition to be satisfied. This is guaranteed to run on the UI thread.
+ * @param timeout timeout in millisecond
+ * @throws TimeoutException when the no event is matched to the given condition within
+ * {@code timeout}
+ */
+ public static void waitOnMainUntil(@NonNull BooleanSupplier condition, long timeout)
+ throws TimeoutException {
+ final AtomicBoolean result = new AtomicBoolean();
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ while (!result.get()) {
+ if (timeout < 0) {
+ throw new TimeoutException();
+ }
+ instrumentation.runOnMainSync(() -> {
+ if (condition.getAsBoolean()) {
+ result.set(true);
+ }
+ });
+ try {
+ Thread.sleep(TIME_SLICE);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ timeout -= TIME_SLICE;
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
new file mode 100644
index 0000000..657420f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.annotation.NonNull;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Helper class to trigger the situation where window in a different process gains focus.
+ */
+public class WindowFocusStealer implements AutoCloseable {
+
+ private final static class MyResultReceiver extends ResultReceiver {
+ final BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<>(1);
+
+ public MyResultReceiver() {
+ super(null);
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ mQueue.add(resultCode);
+ }
+
+ public void waitResult(long timeout) throws TimeoutException {
+ final Object result;
+ try {
+ result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ if (result == null) {
+ throw new TimeoutException();
+ }
+ }
+ }
+
+ @NonNull
+ private final IWindowFocusStealer mService;
+ @NonNull
+ private final Runnable mCloser;
+
+ /**
+ * Let a window in a different process gain the window focus.
+ * @param parentAppWindowToken Token returned from
+ * {@link android.view.View#getApplicationWindowToken()}
+ * @param timeout timeout in millisecond
+ * @throws TimeoutException when failed to have the window focused within {@code timeout}
+ */
+ public void stealWindowFocus(IBinder parentAppWindowToken, long timeout)
+ throws TimeoutException {
+ final MyResultReceiver resultReceiver = new MyResultReceiver();
+ try {
+ mService.stealWindowFocus(parentAppWindowToken, resultReceiver);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ resultReceiver.waitResult(timeout);
+ }
+
+ private WindowFocusStealer(@NonNull IWindowFocusStealer service, @NonNull Runnable closer) {
+ mService = service;
+ mCloser = closer;
+ }
+
+ /**
+ * Establishes a connection to the service.
+ *
+ * @param context {@link Context} to which {@link WindowFocusStealerService} belongs
+ * @param timeout timeout in millisecond
+ * @return
+ * @throws TimeoutException when failed to establish the connection within {@code timeout}
+ */
+ public static WindowFocusStealer connect(Context context, long timeout)
+ throws TimeoutException {
+ final BlockingQueue<IWindowFocusStealer> queue = new ArrayBlockingQueue<>(1);
+
+ final ServiceConnection connection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ queue.add(IWindowFocusStealer.Stub.asInterface(service));
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ }
+ };
+
+ final Intent intent = new Intent();
+ intent.setClass(context, WindowFocusStealerService.class);
+ context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+
+ final IWindowFocusStealer focusStealer;
+ try {
+ focusStealer = queue.poll(timeout, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+
+ if (focusStealer == null) {
+ throw new TimeoutException();
+ }
+
+ final AtomicBoolean closed = new AtomicBoolean(false);
+ return new WindowFocusStealer(focusStealer, () -> {
+ if (closed.compareAndSet(false, true)) {
+ context.unbindService(connection);
+ }
+ });
+ }
+
+ /**
+ * Removes the temporary window and clean up everything.
+ */
+ @Override
+ public void close() throws Exception {
+ mCloser.run();
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
new file mode 100644
index 0000000..ecca4c2
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealerService.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.support.annotation.BinderThread;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public final class WindowFocusStealerService extends Service {
+
+ @Nullable
+ private TextView mCustomView;
+
+ private final class BinderService extends IWindowFocusStealer.Stub {
+ @BinderThread
+ @Override
+ public void stealWindowFocus(IBinder parentAppWindowToken, ResultReceiver resultReceiver) {
+ mMainHandler.post(() -> handleStealWindowFocus(parentAppWindowToken, resultReceiver));
+ }
+ }
+
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new BinderService();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ handleReset();
+ return false;
+ }
+
+ @MainThread
+ public void handleStealWindowFocus(
+ IBinder parentAppWindowToken, ResultReceiver resultReceiver) {
+ handleReset();
+
+ mCustomView = new TextView(this) {
+ private boolean mWindowInitiallyFocused = false;
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (!mWindowInitiallyFocused && hasWindowFocus) {
+ mWindowInitiallyFocused = true;
+ resultReceiver.send(0, null);
+ }
+ }
+ };
+ mCustomView.setText("Popup");
+ mCustomView.setBackground(new ColorDrawable(Color.CYAN));
+ mCustomView.setElevation(0.5f);
+
+ WindowManager mWm = getSystemService(WindowManager.class);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ 150, 150, 10, 10,
+ WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+ PixelFormat.OPAQUE);
+ params.packageName = getPackageName();
+ params.token = parentAppWindowToken;
+
+ mWm.addView(mCustomView, params);
+ }
+
+ @MainThread
+ public void handleReset() {
+ if (mCustomView != null) {
+ getSystemService(WindowManager.class).removeView(mCustomView);
+ mCustomView = null;
+ }
+ }
+
+ @MainThread
+ public void onDestroy() {
+ super.onDestroy();
+ handleReset();
+ }
+
+}
diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk
index 16954f2..230f3e8 100644
--- a/tests/jank/Android.mk
+++ b/tests/jank/Android.mk
@@ -32,7 +32,8 @@
ctstestrunner \
ub-uiautomator \
ub-janktesthelper \
- junit \
- legacy-android-test
+ junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/jdwp/AndroidTest.xml b/tests/jdwp/AndroidTest.xml
index 72d26b1..66b0f94 100644
--- a/tests/jdwp/AndroidTest.xml
+++ b/tests/jdwp/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JDWP test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctsjdwp/java.io.tmpdir" />
diff --git a/tests/leanbackjank/Android.mk b/tests/leanbackjank/Android.mk
index ec114f3..0c6ff9d 100644
--- a/tests/leanbackjank/Android.mk
+++ b/tests/leanbackjank/Android.mk
@@ -32,11 +32,9 @@
compatibility-device-util \
ctstestrunner \
ub-uiautomator \
- ub-janktesthelper \
- android-support-v17-leanback \
- android-support-v7-recyclerview \
- android-support-v4 \
- legacy-android-test
+ ub-janktesthelper
+
+LOCAL_JAVA_LIBRARIES := android.test.base
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/leanbackjank/AndroidTest.xml b/tests/leanbackjank/AndroidTest.xml
index 31504b6..d941325 100644
--- a/tests/leanbackjank/AndroidTest.xml
+++ b/tests/leanbackjank/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS LeanbackJank test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="tv" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/leanbackjank/app/Android.mk b/tests/leanbackjank/app/Android.mk
index 3fc685d..04c3d3f 100644
--- a/tests/leanbackjank/app/Android.mk
+++ b/tests/leanbackjank/app/Android.mk
@@ -27,25 +27,21 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_RESOURCE_DIR := \
- $(TOP)/frameworks/support/v17/leanback/res \
- $(TOP)/frameworks/support/v7/recyclerview/res \
- $(LOCAL_PATH)/res
+LOCAL_USE_AAPT2 := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
ctstestrunner \
ub-uiautomator \
ub-janktesthelper \
- android-support-v4 \
- android-support-v7-recyclerview \
- android-support-v17-leanback \
glide
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay \
- --extra-packages android.support.v17.leanback \
- --extra-packages android.support.v7.recyclerview
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ android-support-v4 \
+ android-support-v7-recyclerview \
+ android-support-v17-leanback
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/tests/leanbackjank/app/AndroidManifest.xml b/tests/leanbackjank/app/AndroidManifest.xml
index 1e1ff3b..1ea3f3f 100644
--- a/tests/leanbackjank/app/AndroidManifest.xml
+++ b/tests/leanbackjank/app/AndroidManifest.xml
@@ -40,6 +40,8 @@
android:label="@string/app_name"
android:logo="@drawable/videos_by_google_banner"
android:theme="@style/Theme.Example.Leanback" >
+ <uses-library android:name="android.test.runner" />
+
<activity
android:name=".ui.MainActivity"
android:icon="@drawable/videos_by_google_banner"
diff --git a/tests/libcore/javautilcollections/AndroidTest.xml b/tests/libcore/javautilcollections/AndroidTest.xml
index 75be288..7ea7634 100644
--- a/tests/libcore/javautilcollections/AndroidTest.xml
+++ b/tests/libcore/javautilcollections/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore java.util Collection test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/libcore/jsr166/AndroidManifest.xml b/tests/libcore/jsr166/AndroidManifest.xml
index baae488..5fd3f6e 100644
--- a/tests/libcore/jsr166/AndroidManifest.xml
+++ b/tests/libcore/jsr166/AndroidManifest.xml
@@ -22,6 +22,9 @@
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.libcore.cts.jsr166"
- android:label="CTS Libcore JSR166 test cases" />
+ android:label="CTS Libcore JSR166 test cases">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index 2a3e5d2..9926f57 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore JSR166 test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -27,8 +28,6 @@
</target_preparer>
<test class="com.android.compatibility.testtype.LibcoreTest" >
<option name="package" value="android.libcore.cts.jsr166" />
- <option name="instrumentation-arg" key="listener"
- value="com.android.cts.runner.CtsTestRunListener" />
<option name="instrumentation-arg" key="filter"
value="com.android.cts.core.runner.ExpectationBasedFilter" />
<option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/luni/AndroidManifest.xml b/tests/libcore/luni/AndroidManifest.xml
index 0389be6..4df4b93 100644
--- a/tests/libcore/luni/AndroidManifest.xml
+++ b/tests/libcore/luni/AndroidManifest.xml
@@ -23,6 +23,9 @@
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.libcore.cts"
- android:label="CTS Libcore test cases" />
+ android:label="CTS Libcore test cases">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index a8c4142..5a397f4 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -27,8 +28,6 @@
</target_preparer>
<test class="com.android.compatibility.testtype.LibcoreTest" >
<option name="package" value="android.libcore.cts" />
- <option name="instrumentation-arg" key="listener"
- value="com.android.cts.runner.CtsTestRunListener" />
<option name="instrumentation-arg" key="filter"
value="com.android.cts.core.runner.ExpectationBasedFilter" />
<option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/ojluni/AndroidManifest.xml b/tests/libcore/ojluni/AndroidManifest.xml
index c010a32..8c45c30 100644
--- a/tests/libcore/ojluni/AndroidManifest.xml
+++ b/tests/libcore/ojluni/AndroidManifest.xml
@@ -21,6 +21,9 @@
<!-- important: instrument another package which actually contains AJUR -->
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.libcore.runner"
- android:label="CTS Libcore OJ test cases" />
+ android:label="CTS Libcore OJ test cases">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index 1cebd4e..a222270 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore OJ test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -22,7 +23,7 @@
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
- <!-- this has the CoreTestRunner which needs to be in a separate APK -->
+ <!-- this has the AJUR which needs to be in a separate APK -->
<option name="test-file-name" value="CtsLibcoreTestRunner.apk" />
<!-- this has just the instrumentation which acts as the tests we want to run -->
<option name="test-file-name" value="CtsLibcoreOjTestCases.apk" />
@@ -31,8 +32,6 @@
<option name="package" value="android.libcore.cts.oj" />
<option name="instrumentation-arg" key="runnerBuilder"
value="com.android.cts.core.runner.support.TestNgRunnerBuilder"/>
- <option name="instrumentation-arg" key="listener"
- value="com.android.cts.runner.CtsTestRunListener"/>
<option name="instrumentation-arg" key="filter"
value="com.android.cts.core.runner.ExpectationBasedFilter" />
<option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/okhttp/AndroidManifest.xml b/tests/libcore/okhttp/AndroidManifest.xml
index 151fda9..dfff563 100644
--- a/tests/libcore/okhttp/AndroidManifest.xml
+++ b/tests/libcore/okhttp/AndroidManifest.xml
@@ -17,12 +17,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.libcore.cts.okhttp">
<uses-permission android:name="android.permission.INTERNET" />
- <application>
+ <application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.libcore.cts.okhttp"
- android:label="CTS Libcore OkHttp test cases" />
+ android:label="CTS Libcore OkHttp test cases">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/libcore/okhttp/AndroidTest.xml b/tests/libcore/okhttp/AndroidTest.xml
index 4e79b80..1445c20 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore OkHttp test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
@@ -27,8 +28,6 @@
</target_preparer>
<test class="com.android.compatibility.testtype.LibcoreTest" >
<option name="package" value="android.libcore.cts.okhttp" />
- <option name="instrumentation-arg" key="listener"
- value="com.android.cts.runner.CtsTestRunListener" />
<option name="instrumentation-arg" key="filter"
value="com.android.cts.core.runner.ExpectationBasedFilter" />
<option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/wycheproof-bc/AndroidManifest.xml b/tests/libcore/wycheproof-bc/AndroidManifest.xml
index 15c5fd5..fb53977 100644
--- a/tests/libcore/wycheproof-bc/AndroidManifest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidManifest.xml
@@ -23,6 +23,9 @@
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.libcore.cts.wycheproof.bouncycastle"
- android:label="CTS Libcore Wycheproof Bouncy Castle test cases" />
+ android:label="CTS Libcore Wycheproof Bouncy Castle test cases">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index 9c3a50a..e5280a4 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore Wycheproof Bouncy Castle test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
@@ -26,8 +27,6 @@
context of one of the suites, so we have to limit the test
infrastructure to only running the test suites. -->
<option name="test-package" value="android.libcore.cts.wycheproof" />
- <option name="instrumentation-arg" key="listener"
- value="com.android.cts.runner.CtsTestRunListener" />
<option name="instrumentation-arg" key="filter"
value="com.android.cts.core.runner.ExpectationBasedFilter" />
<option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/libcore/wycheproof/AndroidManifest.xml b/tests/libcore/wycheproof/AndroidManifest.xml
index 765c677..5e8058f 100644
--- a/tests/libcore/wycheproof/AndroidManifest.xml
+++ b/tests/libcore/wycheproof/AndroidManifest.xml
@@ -23,6 +23,9 @@
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.libcore.cts.wycheproof.conscrypt"
- android:label="CTS Libcore Wycheproof Conscrypt test cases" />
+ android:label="CTS Libcore Wycheproof Conscrypt test cases">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
</manifest>
diff --git a/tests/libcore/wycheproof/AndroidTest.xml b/tests/libcore/wycheproof/AndroidTest.xml
index b5874fd..2a1f2b5 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Libcore Wycheproof Conscrypt test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
@@ -26,8 +27,6 @@
context of one of the suites, so we have to limit the test
infrastructure to only running the test suites. -->
<option name="test-package" value="android.libcore.cts.wycheproof" />
- <option name="instrumentation-arg" key="listener"
- value="com.android.cts.runner.CtsTestRunListener" />
<option name="instrumentation-arg" key="filter"
value="com.android.cts.core.runner.ExpectationBasedFilter" />
<option name="core-expectation" value="/knownfailures.txt" />
diff --git a/tests/mocking/AndroidTest.xml b/tests/mocking/AndroidTest.xml
index 2741eba..58340c5 100644
--- a/tests/mocking/AndroidTest.xml
+++ b/tests/mocking/AndroidTest.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
<configuration description="Config for Mockito mocking test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/mocking/inline/AndroidTest.xml b/tests/mocking/inline/AndroidTest.xml
index e642329..d65d6f5 100644
--- a/tests/mocking/inline/AndroidTest.xml
+++ b/tests/mocking/inline/AndroidTest.xml
@@ -15,6 +15,7 @@
~ limitations under the License.
-->
<configuration description="Config for Mockito inline mocking test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/netlegacy22.api/Android.mk b/tests/netlegacy22.api/Android.mk
index 8639514..5a330e5 100644
--- a/tests/netlegacy22.api/Android.mk
+++ b/tests/netlegacy22.api/Android.mk
@@ -27,7 +27,7 @@
LOCAL_SDK_VERSION := 22
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/netlegacy22.permission/Android.mk b/tests/netlegacy22.permission/Android.mk
index 262e600..f5cc38b 100644
--- a/tests/netlegacy22.permission/Android.mk
+++ b/tests/netlegacy22.permission/Android.mk
@@ -27,7 +27,7 @@
LOCAL_SDK_VERSION := 22
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/netlegacy22.permission/AndroidTest.xml b/tests/netlegacy22.permission/AndroidTest.xml
index db6f294..83983a7 100644
--- a/tests/netlegacy22.permission/AndroidTest.xml
+++ b/tests/netlegacy22.permission/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Legacy android.net Permission test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/openglperf2/AndroidTest.xml b/tests/openglperf2/AndroidTest.xml
index 5d8f9d1..4ac0455 100644
--- a/tests/openglperf2/AndroidTest.xml
+++ b/tests/openglperf2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS OpenGL test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="graphics" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/pdf/Android.mk b/tests/pdf/Android.mk
index 2b301af..53ec66c 100644
--- a/tests/pdf/Android.mk
+++ b/tests/pdf/Android.mk
@@ -28,8 +28,7 @@
compatibility-device-util \
ctstestrunner \
android-support-annotations \
- junit \
- legacy-android-test
+ junit
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/pdf/AndroidTest.xml b/tests/pdf/AndroidTest.xml
index ab88726..e237461 100644
--- a/tests/pdf/AndroidTest.xml
+++ b/tests/pdf/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Pdf test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="component" value="framework" />
diff --git a/tests/pdf/res/raw/protected_pdf.pdf b/tests/pdf/res/raw/protected_pdf.pdf
new file mode 100644
index 0000000..e4092b9
--- /dev/null
+++ b/tests/pdf/res/raw/protected_pdf.pdf
Binary files differ
diff --git a/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java b/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java
index 2f0158e..2ce69d1 100644
--- a/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java
+++ b/tests/pdf/src/android/graphics/pdf/cts/PdfRendererTest.java
@@ -62,6 +62,7 @@
private static final int A5_PORTRAIT_PRINTSCALING_NONE =
R.raw.a5_portrait_rgbb_1_6_printscaling_none;
private static final int TWO_PAGES = R.raw.two_pages;
+ private static final int PROTECTED_PDF = R.raw.protected_pdf;
private Context mContext;
@@ -76,12 +77,28 @@
}
@Test
- @Ignore("Makes all subsequent tests fail")
public void constructRendererFromNonPDF() throws Exception {
// Open jpg as if it was a PDF
- ParcelFileDescriptor fd = mContext.getResources().openRawResourceFd(R.raw.testimage)
- .getParcelFileDescriptor();
- verifyException(() -> new PdfRenderer(fd), IOException.class);
+ verifyException(() -> createRenderer(R.raw.testimage, mContext), IOException.class);
+ }
+
+ @Test
+ public void constructRendererFromProtectedPDF() throws Exception {
+ verifyException(() -> createRenderer(PROTECTED_PDF, mContext), SecurityException.class);
+ }
+
+ @Test
+ public void rendererRecoversAfterFailure() throws Exception {
+ // Create rendered to prevent lib from being unloaded
+ PdfRenderer firstRenderer = createRenderer(A4_PORTRAIT, mContext);
+
+ verifyException(() -> createRenderer(PROTECTED_PDF, mContext), SecurityException.class);
+
+ // We can create new renderers after we failed to create one
+ PdfRenderer renderer = createRenderer(TWO_PAGES, mContext);
+ renderer.close();
+
+ firstRenderer.close();
}
@Test
diff --git a/tests/sample/Android.mk b/tests/sample/Android.mk
index f6d8760..5602e7e 100755
--- a/tests/sample/Android.mk
+++ b/tests/sample/Android.mk
@@ -27,8 +27,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- android-support-test \
- legacy-android-test
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index c8d1949..8a7d2d9 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Sample test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/security/Android.mk b/tests/security/Android.mk
new file mode 100755
index 0000000..aecc076
--- /dev/null
+++ b/tests/security/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle bouncycastle-bcpkix guava
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE := cts-security-test-support-library
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/keystore/src/android/keystore/cts/Asn1Utils.java b/tests/security/src/android/keystore/cts/Asn1Utils.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/Asn1Utils.java
rename to tests/security/src/android/keystore/cts/Asn1Utils.java
diff --git a/tests/tests/keystore/src/android/keystore/cts/Attestation.java b/tests/security/src/android/keystore/cts/Attestation.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/Attestation.java
rename to tests/security/src/android/keystore/cts/Attestation.java
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestationApplicationId.java b/tests/security/src/android/keystore/cts/AttestationApplicationId.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/AttestationApplicationId.java
rename to tests/security/src/android/keystore/cts/AttestationApplicationId.java
diff --git a/tests/security/src/android/keystore/cts/AttestationPackageInfo.java b/tests/security/src/android/keystore/cts/AttestationPackageInfo.java
new file mode 100644
index 0000000..3c3e2bd
--- /dev/null
+++ b/tests/security/src/android/keystore/cts/AttestationPackageInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.keystore.cts;
+
+import com.android.org.bouncycastle.asn1.ASN1Encodable;
+import com.android.org.bouncycastle.asn1.ASN1Sequence;
+
+import java.security.cert.CertificateParsingException;
+
+import java.io.UnsupportedEncodingException;
+
+public class AttestationPackageInfo implements java.lang.Comparable<AttestationPackageInfo> {
+ private static final int PACKAGE_NAME_INDEX = 0;
+ private static final int VERSION_INDEX = 1;
+
+ private final String packageName;
+ private final long version;
+
+ public AttestationPackageInfo(String packageName, long version) {
+ this.packageName = packageName;
+ this.version = version;
+ }
+
+ public AttestationPackageInfo(ASN1Encodable asn1Encodable) throws CertificateParsingException {
+ if (!(asn1Encodable instanceof ASN1Sequence)) {
+ throw new CertificateParsingException(
+ "Expected sequence for AttestationPackageInfo, found "
+ + asn1Encodable.getClass().getName());
+ }
+
+ ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
+ try {
+ packageName = Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(
+ sequence.getObjectAt(PACKAGE_NAME_INDEX));
+ } catch (UnsupportedEncodingException e) {
+ throw new CertificateParsingException(
+ "Converting octet stream to String triggered an UnsupportedEncodingException",
+ e);
+ }
+ version = Asn1Utils.getLongFromAsn1(sequence.getObjectAt(VERSION_INDEX));
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append("Package name: ").append(getPackageName())
+ .append("\nVersion: " + getVersion()).toString();
+ }
+
+ @Override
+ public int compareTo(AttestationPackageInfo other) {
+ int res = packageName.compareTo(other.packageName);
+ if (res != 0) return res;
+ res = Long.compare(version, other.version);
+ if (res != 0) return res;
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof AttestationPackageInfo)
+ && (0 == compareTo((AttestationPackageInfo) o));
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AuthorizationList.java b/tests/security/src/android/keystore/cts/AuthorizationList.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/AuthorizationList.java
rename to tests/security/src/android/keystore/cts/AuthorizationList.java
diff --git a/tests/security/src/android/keystore/cts/CertificateUtils.java b/tests/security/src/android/keystore/cts/CertificateUtils.java
new file mode 100644
index 0000000..68e936e
--- /dev/null
+++ b/tests/security/src/android/keystore/cts/CertificateUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 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.keystore.cts;
+
+import com.android.org.bouncycastle.asn1.x500.X500Name;
+import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.org.bouncycastle.cert.X509CertificateHolder;
+import com.android.org.bouncycastle.cert.X509v3CertificateBuilder;
+import com.android.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import javax.security.auth.x500.X500Principal;
+
+public class CertificateUtils {
+ /** Creates a self-signed X.509 certificate, given a key pair, subject and issuer. */
+ public static X509Certificate createCertificate(
+ KeyPair keyPair, X500Principal subject, X500Principal issuer) throws Exception {
+ // Make the certificate valid for two days.
+ long millisPerDay = 24 * 60 * 60 * 1000;
+ long now = System.currentTimeMillis();
+ Date start = new Date(now - millisPerDay);
+ Date end = new Date(now + millisPerDay);
+
+ // Assign a random serial number.
+ byte[] serialBytes = new byte[16];
+ new SecureRandom().nextBytes(serialBytes);
+ BigInteger serialNumber = new BigInteger(1, serialBytes);
+
+ // Create the certificate builder
+ X509v3CertificateBuilder x509cg =
+ new X509v3CertificateBuilder(
+ X500Name.getInstance(issuer.getEncoded()),
+ serialNumber,
+ start,
+ end,
+ X500Name.getInstance(subject.getEncoded()),
+ SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
+
+ // Choose a signature algorithm matching the key format.
+ String keyAlgorithm = keyPair.getPrivate().getAlgorithm();
+ String signatureAlgorithm;
+ if (keyAlgorithm.equals("RSA")) {
+ signatureAlgorithm = "SHA256withRSA";
+ } else if (keyAlgorithm.equals("EC")) {
+ signatureAlgorithm = "SHA256withECDSA";
+ } else {
+ throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
+ }
+
+ // Sign the certificate and generate it.
+ X509CertificateHolder x509holder =
+ x509cg.build(
+ new JcaContentSignerBuilder(signatureAlgorithm)
+ .build(keyPair.getPrivate()));
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate x509c =
+ (X509Certificate)
+ certFactory.generateCertificate(
+ new ByteArrayInputStream(x509holder.getEncoded()));
+ return x509c;
+ }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RootOfTrust.java b/tests/security/src/android/keystore/cts/RootOfTrust.java
similarity index 100%
rename from tests/tests/keystore/src/android/keystore/cts/RootOfTrust.java
rename to tests/security/src/android/keystore/cts/RootOfTrust.java
diff --git a/tests/sensor/Android.mk b/tests/sensor/Android.mk
index dcf9015..e636af8 100644
--- a/tests/sensor/Android.mk
+++ b/tests/sensor/Android.mk
@@ -27,7 +27,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
-LOCAL_JAVA_LIBRARIES := platform-test-annotations
+LOCAL_JAVA_LIBRARIES := platform-test-annotations android.test.base.stubs
LOCAL_SDK_VERSION := current
@@ -89,7 +89,7 @@
LOCAL_SDK_VERSION := current
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_NDK_STL_VARIANT := c++_shared
diff --git a/tests/sensor/AndroidTest.xml b/tests/sensor/AndroidTest.xml
index 3eff64c..7137323 100644
--- a/tests/sensor/AndroidTest.xml
+++ b/tests/sensor/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Sensor test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="location" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/signature/api-check/Android.mk b/tests/signature/api-check/Android.mk
index 1d616cb..15fa178 100644
--- a/tests/signature/api-check/Android.mk
+++ b/tests/signature/api-check/Android.mk
@@ -30,7 +30,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-signature-common \
- repackaged-legacy-test \
+ repackaged.android.test.base \
repackaged.android.test.runner \
repackaged.android.test.mock \
diff --git a/tests/signature/api-check/android-test-base-27-api/Android.mk b/tests/signature/api-check/android-test-base-27-api/Android.mk
new file mode 100644
index 0000000..1adcb12c
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-27-api/Android.mk
@@ -0,0 +1,24 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAndroidTestBase27ApiSignatureTestCases
+
+LOCAL_SIGNATURE_API_FILES := \
+ android-test-base-current.api \
+
+include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
new file mode 100644
index 0000000..c65bfdf
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.signature.cts.api.android_test_base_26">
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+ <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="27"/>
+
+ <application/>
+
+ <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+ android:targetPackage="android.signature.cts.api.android_test_base_27"
+ android:label="Android Test Base 27 API Signature Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml
new file mode 100644
index 0000000..0362d58
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-27-api/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Android Test Base 27 API Signature test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="systems" />
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="android-test-base-current.api->/data/local/tmp/signature-test/android-test-base-current.api" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsAndroidTestBase27ApiSignatureTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.signature.cts.api.android_test_base_27" />
+ <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+ <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api" />
+ <option name="runtime-hint" value="5s" />
+ </test>
+</configuration>
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
index 16b29a8..a180ad0 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Android Test Mock Current API Signature test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
index ecf7055..dc16296 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Android Test Runner Current API Signature test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/Android.mk b/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
index df69004..b382698 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
+++ b/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
@@ -19,6 +19,6 @@
LOCAL_PACKAGE_NAME := CtsApacheHttpLegacyCurrentApiSignatureTestCases
LOCAL_SIGNATURE_API_FILES := \
- apache-http-legacy-current.api \
+ apache-http-legacy-minus-current.api \
include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
index a5e69a9..4ab686e 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
@@ -14,13 +14,14 @@
limitations under the License.
-->
<configuration description="Config for CTS Apache Http Legacy Current API Signature test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
<option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
- <option name="push" value="apache-http-legacy-current.api->/data/local/tmp/signature-test/apache-http-legacy-current.api" />
+ <option name="push" value="apache-http-legacy-minus-current.api->/data/local/tmp/signature-test/apache-http-legacy-minus-current.api" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
@@ -29,7 +30,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.signature.cts.api.apache_http_legacy_current" />
<option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
- <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.api" />
+ <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-minus-current.api" />
<option name="runtime-hint" value="5s" />
</test>
</configuration>
diff --git a/tests/signature/api-check/current-api/AndroidTest.xml b/tests/signature/api-check/current-api/AndroidTest.xml
index 29dfe6d..eac4cae 100644
--- a/tests/signature/api-check/current-api/AndroidTest.xml
+++ b/tests/signature/api-check/current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Current API Signature test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/signature/api-check/legacy-test-26-api/Android.mk b/tests/signature/api-check/legacy-test-26-api/Android.mk
deleted file mode 100644
index 699bd0c..0000000
--- a/tests/signature/api-check/legacy-test-26-api/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsLegacyTest26ApiSignatureTestCases
-
-LOCAL_SIGNATURE_API_FILES := \
- legacy-test-current.api \
-
-include $(LOCAL_PATH)/../build_signature_apk.mk
diff --git a/tests/signature/api-check/legacy-test-26-api/AndroidManifest.xml b/tests/signature/api-check/legacy-test-26-api/AndroidManifest.xml
deleted file mode 100644
index 636dfd3..0000000
--- a/tests/signature/api-check/legacy-test-26-api/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.signature.cts.api.legacy_test_26">
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-
- <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="26"/>
-
- <application/>
-
- <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
- android:targetPackage="android.signature.cts.api.legacy_test_26"
- android:label="Legacy Test 26 API Signature Test"/>
-
-</manifest>
diff --git a/tests/signature/api-check/legacy-test-26-api/AndroidTest.xml b/tests/signature/api-check/legacy-test-26-api/AndroidTest.xml
deleted file mode 100644
index ecb3299..0000000
--- a/tests/signature/api-check/legacy-test-26-api/AndroidTest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Config for CTS Legacy Test 26 API Signature test cases">
- <option name="config-descriptor:metadata" key="component" value="systems" />
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
- <option name="teardown-command" value="rm -rf /data/local/tmp/signature-test" />
- </target_preparer>
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
- <option name="push" value="legacy-test-current.api->/data/local/tmp/signature-test/legacy-test-current.api" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsLegacyTest26ApiSignatureTestCases.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.signature.cts.api.legacy_test_26" />
- <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
- <option name="instrumentation-arg" key="expected-api-files" value="legacy-test-current.api" />
- <option name="runtime-hint" value="5s" />
- </test>
-</configuration>
diff --git a/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java b/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java
new file mode 100644
index 0000000..79b6374
--- /dev/null
+++ b/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.api;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.stream.Stream;
+
+import android.signature.cts.ClassProvider;
+import dalvik.system.DexFile;
+
+@SuppressWarnings("deprecation")
+public class BootClassPathClassesProvider extends ClassProvider {
+ private Stream<Class<?>> allClasses = null;
+
+ @Override
+ public Stream<Class<?>> getAllClasses() {
+ Stream.Builder<Class<?>> builder = Stream.builder();
+ if (allClasses == null) {
+ for (String file : getBootJarPaths()) {
+ try {
+ DexFile dexFile = new DexFile(file);
+ Enumeration<String> entries = dexFile.entries();
+ while (entries.hasMoreElements()) {
+ String className = entries.nextElement();
+ Class<?> clazz = getClass(className);
+ if (clazz != null) {
+ builder.add(clazz);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to parse dex in " + file, e);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Error while loading class in " + file, e);
+ }
+ }
+ allClasses = builder.build();
+ }
+ return allClasses;
+ }
+
+ private String[] getBootJarPaths() {
+ return System.getProperty("java.boot.class.path").split(":");
+ }
+}
\ No newline at end of file
diff --git a/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java b/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
index 3473cfd..37ca114 100644
--- a/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
+++ b/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
@@ -16,13 +16,8 @@
package android.signature.cts.api;
-import android.os.Bundle;
-import android.signature.cts.ApiDocumentParser;
-import android.signature.cts.ApiComplianceChecker;
-import android.signature.cts.FailureType;
-import android.signature.cts.JDiffClassDescription;
-import android.signature.cts.ReflectionHelper;
-import android.signature.cts.ResultObserver;
+import static android.signature.cts.CurrentApi.API_FILE_DIRECTORY;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -30,12 +25,21 @@
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
+
import org.xmlpull.v1.XmlPullParserException;
+
+import android.os.Bundle;
+import android.signature.cts.ApiComplianceChecker;
+import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.ExcludingClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import android.signature.cts.ReflectionHelper;
+import android.signature.cts.ResultObserver;
import repackaged.android.test.InstrumentationTestCase;
import repackaged.android.test.InstrumentationTestRunner;
-import static android.signature.cts.CurrentApi.API_FILE_DIRECTORY;
-
/**
* Performs the signature check via a JUnit test.
*/
@@ -58,12 +62,16 @@
KNOWN_INACCESSIBLE_CLASSES.add("android.os.UserManager.UserRestrictionSource");
KNOWN_INACCESSIBLE_CLASSES.add(
"android.service.persistentdata.PersistentDataBlockManager.FlashLockState");
+ KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.IdentifierType");
+ KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.ProgramSelector.ProgramType");
+ KNOWN_INACCESSIBLE_CLASSES.add("android.hardware.radio.RadioManager.Band");
}
private TestResultObserver mResultObserver;
private String[] expectedApiFiles;
private String[] unexpectedApiFiles;
+ private String annotationForExactMatch;
private class TestResultObserver implements ResultObserver {
@@ -94,6 +102,7 @@
expectedApiFiles = getCommaSeparatedList(instrumentationArgs, "expected-api-files");
unexpectedApiFiles = getCommaSeparatedList(instrumentationArgs, "unexpected-api-files");
+ annotationForExactMatch = instrumentationArgs.getString("annotation-for-exact-match");
}
private String[] getCommaSeparatedList(Bundle instrumentationArgs, String key) {
@@ -111,9 +120,19 @@
*/
public void testSignature() {
try {
+
+ // Prepare for a class provider that loads classes from bootclasspath but filters
+ // out known inaccessible classes.
+ // Note that com.android.internal.R.* inner classes are also excluded as they are
+ // not part of API though exist in the runtime.
+ ClassProvider classProvider = new ExcludingClassProvider(
+ new BootClassPathClassesProvider(),
+ name -> KNOWN_INACCESSIBLE_CLASSES.contains(name)
+ || (name != null && name.startsWith("com.android.internal.R.")));
+
Set<JDiffClassDescription> unexpectedClasses = loadUnexpectedClasses();
for (JDiffClassDescription classDescription : unexpectedClasses) {
- Class<?> unexpectedClass = findUnexpectedClass(classDescription);
+ Class<?> unexpectedClass = findUnexpectedClass(classDescription, classProvider);
if (unexpectedClass != null) {
mResultObserver.notifyFailure(
FailureType.UNEXPECTED_CLASS,
@@ -122,7 +141,9 @@
}
}
- ApiComplianceChecker complianceChecker = new ApiComplianceChecker(mResultObserver);
+ ApiComplianceChecker complianceChecker = new ApiComplianceChecker(mResultObserver,
+ classProvider);
+ complianceChecker.setAnnotationForExactMatch(annotationForExactMatch);
ApiDocumentParser apiDocumentParser = new ApiDocumentParser(
TAG, new ApiDocumentParser.Listener() {
@Override
@@ -143,6 +164,9 @@
File file = new File(API_FILE_DIRECTORY + "/" + expectedApiFile);
apiDocumentParser.parse(new FileInputStream(file));
}
+
+ // After done parsing all expected API files, check for the exact match.
+ complianceChecker.checkExactMatch();
} catch (Exception e) {
mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getMessage(),
e.getMessage());
@@ -159,9 +183,10 @@
}
}
- private Class<?> findUnexpectedClass(JDiffClassDescription classDescription) {
+ private Class<?> findUnexpectedClass(JDiffClassDescription classDescription,
+ ClassProvider classProvider) {
try {
- return ReflectionHelper.findMatchingClass(classDescription);
+ return ReflectionHelper.findMatchingClass(classDescription, classProvider);
} catch (ClassNotFoundException e) {
return null;
}
diff --git a/tests/signature/api-check/system-current-api/Android.mk b/tests/signature/api-check/system-current-api/Android.mk
index 1c10d2c..34c6c22 100644
--- a/tests/signature/api-check/system-current-api/Android.mk
+++ b/tests/signature/api-check/system-current-api/Android.mk
@@ -20,6 +20,7 @@
LOCAL_SIGNATURE_API_FILES := \
system-current.api \
+ system-removed.api \
android-test-mock-current.api \
android-test-runner-current.api \
diff --git a/tests/signature/api-check/system-current-api/AndroidTest.xml b/tests/signature/api-check/system-current-api/AndroidTest.xml
index 6bf641a..f958e3d 100644
--- a/tests/signature/api-check/system-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/system-current-api/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS System Current API Signature test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="mkdir -p /data/local/tmp/signature-test" />
@@ -23,6 +24,9 @@
<option name="push" value="system-current.api->/data/local/tmp/signature-test/system-current.api" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="system-removed.api->/data/local/tmp/signature-test/system-removed.api" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="push" value="android-test-mock-current.api->/data/local/tmp/signature-test/android-test-mock-current.api" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
@@ -35,8 +39,9 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.signature.cts.api.system_current" />
<option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
- <option name="instrumentation-arg" key="expected-api-files" value="system-current.api" />
+ <option name="instrumentation-arg" key="expected-api-files" value="system-current.api,system-removed.api" />
<option name="instrumentation-arg" key="unexpected-api-files" value="android-test-mock-current.api,android-test-runner-current.api" />
+ <option name="instrumentation-arg" key="annotation-for-exact-match" value="android.annotation.SystemApi" />
<option name="runtime-hint" value="30s" />
</test>
</configuration>
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
index 9be5e06..abeae54 100644
--- a/tests/signature/api/Android.mk
+++ b/tests/signature/api/Android.mk
@@ -48,12 +48,12 @@
include $(LOCAL_PATH)/build_xml_api_file.mk
-# current legacy-test api, in XML format.
+# current android-test-base api, in XML format.
# ============================================================
include $(CLEAR_VARS)
-LOCAL_MODULE := cts-legacy-test-current-api
-LOCAL_MODULE_STEM := legacy-test-current.api
-LOCAL_SRC_FILES := frameworks/base/legacy-test/api/legacy-test-current.txt
+LOCAL_MODULE := cts-android-test-base-current-api
+LOCAL_MODULE_STEM := android-test-base-current.api
+LOCAL_SRC_FILES := frameworks/base/test-base/api/android-test-base-current.txt
include $(LOCAL_PATH)/build_xml_api_file.mk
@@ -75,11 +75,28 @@
include $(LOCAL_PATH)/build_xml_api_file.mk
-# current apache-http-legacy api, in XML format.
-# ==============================================
+# current apache-http-legacy minus current api, in text format.
+# =============================================================
+# Removes any classes from the org.apache.http.legacy API description that are
+# also part of the Android API description.
include $(CLEAR_VARS)
-LOCAL_MODULE := cts-apache-http-legacy-current-api
-LOCAL_MODULE_STEM := apache-http-legacy-current.api
-LOCAL_SRC_FILES := external/apache-http/api/apache-http-legacy-current.txt
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_ETC)
-include $(LOCAL_PATH)/build_xml_api_file.mk
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := cts-apache-http-legacy-minus-current-api
+LOCAL_MODULE_STEM := apache-http-legacy-minus-current.api
+
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE) : \
+ frameworks/base/api/current.txt \
+ external/apache-http/api/apache-http-legacy-current.txt \
+ | $(APICHECK)
+ @echo "Generate unique Apache Http Legacy API file -> $@"
+ @mkdir -p $(dir $@)
+ $(hide) $(APICHECK_COMMAND) -new_api_no_strip \
+ frameworks/base/api/current.txt \
+ external/apache-http/api/apache-http-legacy-current.txt \
+ $@
diff --git a/tests/signature/intent-check/AndroidTest.xml b/tests/signature/intent-check/AndroidTest.xml
index da6895b..134a175 100644
--- a/tests/signature/intent-check/AndroidTest.xml
+++ b/tests/signature/intent-check/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Intent Signature test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
diff --git a/tests/signature/runSignatureTests.sh b/tests/signature/runSignatureTests.sh
index 623973d..e23d4fa 100755
--- a/tests/signature/runSignatureTests.sh
+++ b/tests/signature/runSignatureTests.sh
@@ -15,7 +15,7 @@
CtsSystemCurrentApiSignatureTestCases
CtsAndroidTestMockCurrentApiSignatureTestCases
CtsAndroidTestRunnerCurrentApiSignatureTestCases
-CtsLegacyTest26ApiSignatureTestCases
+CtsAndroidTestBase27ApiSignatureTestCases
CtsApacheHttpLegacyCurrentApiSignatureTestCases
"
else
diff --git a/tests/signature/src/android/signature/cts/ApiComplianceChecker.java b/tests/signature/src/android/signature/cts/ApiComplianceChecker.java
index 8dbf5e7..57ad676 100644
--- a/tests/signature/src/android/signature/cts/ApiComplianceChecker.java
+++ b/tests/signature/src/android/signature/cts/ApiComplianceChecker.java
@@ -15,6 +15,7 @@
*/
package android.signature.cts;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -71,9 +72,80 @@
private final ResultObserver resultObserver;
+ private final ClassProvider classProvider;
- public ApiComplianceChecker(ResultObserver resultObserver) {
+ private Class<? extends Annotation> annotationClass = null;
+
+ private Map<String, Class<?>> annotatedClassesMap = new HashMap<>();
+ private Map<String, Set<Constructor<?>>> annotatedConstructorsMap = new HashMap<>();
+ private Map<String, Set<Method>> annotatedMethodsMap = new HashMap<>();
+ private Map<String, Set<Field>> annotatedFieldsMap = new HashMap<>();
+
+ public ApiComplianceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
this.resultObserver = resultObserver;
+ this.classProvider = classProvider;
+ }
+
+ /**
+ * Optionally let ApiComplianceCheker to ensure runtime classes do not have more API members
+ * than the documented API ones.
+ *
+ * @param annotationName name of the annotation class for the API type (e.g.
+ * android.annotation.SystemApi)
+ */
+ public void setAnnotationForExactMatch(String annotationName) {
+ annotationClass = ReflectionHelper.getAnnotationClass(annotationName);
+ classProvider.getAllClasses().forEach(clazz -> {
+ if (clazz.isAnnotationPresent(annotationClass)) {
+ annotatedClassesMap.put(clazz.getName(), clazz);
+ }
+ Set<Constructor<?>> constructors = ReflectionHelper.getAnnotatedConstructors(clazz,
+ annotationClass);
+ if (!constructors.isEmpty()) {
+ annotatedConstructorsMap.put(clazz.getName(), constructors);
+ }
+ Set<Method> methods = ReflectionHelper.getAnnotatedMethods(clazz, annotationClass);
+ if (!methods.isEmpty()) {
+ annotatedMethodsMap.put(clazz.getName(), methods);
+ }
+ Set<Field> fields = ReflectionHelper.getAnnotatedFields(clazz, annotationClass);
+ if (!fields.isEmpty()) {
+ annotatedFieldsMap.put(clazz.getName(), fields);
+ }
+ });
+ }
+
+ /**
+ * If an annotated API (class, method, constructor, fields) found in the device is not in the
+ * documented API then trigger an error.
+ */
+ public void checkExactMatch() {
+ for (Class<?> clazz : annotatedClassesMap.values()) {
+ resultObserver.notifyFailure(FailureType.EXTRA_CLASS, clazz.getName(),
+ "Class annotated with " + annotationClass.getName()
+ + " does not exist in the documented API");
+ }
+ for (Set<Constructor<?>> set : annotatedConstructorsMap.values()) {
+ for (Constructor<?> c : set) {
+ resultObserver.notifyFailure(FailureType.EXTRA_METHOD, c.toString(),
+ "Constructor annotated with " + annotationClass.getName()
+ + " does not exist in the API");
+ }
+ }
+ for (Set<Method> set : annotatedMethodsMap.values()) {
+ for (Method m : set) {
+ resultObserver.notifyFailure(FailureType.EXTRA_METHOD, m.toString(),
+ "Method annotated with " + annotationClass.getName()
+ + " does not exist in the API");
+ }
+ }
+ for (Set<Field> set : annotatedFieldsMap.values()) {
+ for (Field f : set) {
+ resultObserver.notifyFailure(FailureType.EXTRA_FIELD, f.toString(),
+ "Field annotated with " + annotationClass.getName()
+ + " does not exist in the API");
+ }
+ }
}
private static void loge(String message, Exception exception) {
@@ -100,6 +172,9 @@
public void checkSignatureCompliance(JDiffClassDescription classDescription) {
Class<?> runtimeClass = checkClassCompliance(classDescription);
if (runtimeClass != null) {
+ // remove the class from the set if found
+ annotatedClassesMap.remove(runtimeClass.getName());
+
checkFieldsCompliance(classDescription, runtimeClass);
checkConstructorCompliance(classDescription, runtimeClass);
checkMethodCompliance(classDescription, runtimeClass);
@@ -188,7 +263,7 @@
private Class<?> findRequiredClass(JDiffClassDescription classDescription) {
try {
- return ReflectionHelper.findMatchingClass(classDescription);
+ return ReflectionHelper.findMatchingClass(classDescription, classProvider);
} catch (ClassNotFoundException e) {
loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
return null;
@@ -357,9 +432,11 @@
Class<?> runtimeClass) {
// A map of field name to field of the fields contained in runtimeClass.
Map<String, Field> classFieldMap = buildFieldMap(runtimeClass);
+ Set<Field> annotatedFields = annotatedFieldsMap.get(runtimeClass.getName());
for (JDiffClassDescription.JDiffField field : classDescription.getFields()) {
try {
Field f = classFieldMap.get(field.mName);
+ boolean found = false;
if (f == null) {
resultObserver.notifyFailure(FailureType.MISSING_FIELD,
field.toReadableString(classDescription.getAbsoluteClassName()),
@@ -390,8 +467,25 @@
"Non-compatible field type found when looking for " +
field.toSignatureString());
}
+ found = true;
+ } else {
+ found = true;
}
+ if (found) {
+ // make sure that the field (or its declaring class) is annotated
+ if (annotationClass != null) {
+ if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(f, annotationClass)) {
+ resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION,
+ f.toString(),
+ "Annotation " + annotationClass.getName() + " is missing");
+ }
+ }
+ // remove it from the set if found in the API doc
+ if (annotatedFields != null) {
+ annotatedFields.remove(f);
+ }
+ }
} catch (Exception e) {
loge("Got exception when checking field compliance", e);
resultObserver.notifyFailure(
@@ -623,6 +717,8 @@
@SuppressWarnings("unchecked")
private void checkConstructorCompliance(JDiffClassDescription classDescription,
Class<?> runtimeClass) {
+ Set<Constructor<?>> annotatedConstructors = annotatedConstructorsMap
+ .get(runtimeClass.getName());
for (JDiffClassDescription.JDiffConstructor con : classDescription.getConstructors()) {
try {
Constructor<?> c = ReflectionHelper.findMatchingConstructor(runtimeClass, con);
@@ -642,6 +738,19 @@
"Non-compatible method found when looking for " +
con.toSignatureString());
}
+ // make sure that the constructor (or its declaring class) is annotated
+ if (annotationClass != null) {
+ if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(c, annotationClass)) {
+ resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION,
+ c.toString(),
+ "Annotation " + annotationClass.getName() + " is missing");
+ }
+ }
+
+ // remove it from the set if found in the API doc
+ if (annotatedConstructors != null) {
+ annotatedConstructors.remove(c);
+ }
}
} catch (Exception e) {
loge("Got exception when checking constructor compliance", e);
@@ -661,6 +770,7 @@
*/
private void checkMethodCompliance(JDiffClassDescription classDescription,
Class<?> runtimeClass) {
+ Set<Method> annotatedMethods = annotatedMethodsMap.get(runtimeClass.getName());
for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
try {
@@ -692,6 +802,22 @@
"Non-compatible method found when looking for " +
method.toSignatureString());
}
+ // make sure that the method (or its declaring class) is annotated or overriding
+ // annotated method.
+ if (annotationClass != null) {
+ if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(m, annotationClass)
+ && !ReflectionHelper.isOverridingAnnotatedMethod(m,
+ annotationClass)) {
+ resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION,
+ m.toString(),
+ "Annotation " + annotationClass.getName() + " is missing");
+ }
+ }
+
+ // remove it from the set if found in the API doc
+ if (annotatedMethods != null) {
+ annotatedMethods.remove(m);
+ }
}
} catch (Exception e) {
loge("Got exception when checking method compliance", e);
diff --git a/tests/signature/src/android/signature/cts/ClassProvider.java b/tests/signature/src/android/signature/cts/ClassProvider.java
new file mode 100644
index 0000000..2461f26
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/ClassProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.signature.cts;
+
+import java.util.stream.Stream;
+
+/**
+ * Generic class for getting runtime classes that will be matched against
+ * {@link JDiffClassDescription}. {@link ApiComplianceChecker} is using this
+ * when it needs runtime classes.
+ */
+public abstract class ClassProvider {
+ /**
+ * Get a specific class with the given name.
+ *
+ * @throws ClassNotFoundException
+ */
+ public Class<?> getClass(String name) throws ClassNotFoundException {
+ return Class.forName(name, false, this.getClass().getClassLoader());
+ }
+
+ /**
+ * Gets all classes available to this provider.
+ */
+ public abstract Stream<Class<?>> getAllClasses();
+}
diff --git a/tests/signature/src/android/signature/cts/ExcludingClassProvider.java b/tests/signature/src/android/signature/cts/ExcludingClassProvider.java
new file mode 100644
index 0000000..290cbf5
--- /dev/null
+++ b/tests/signature/src/android/signature/cts/ExcludingClassProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 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.signature.cts;
+
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * A filtered class provider which excludes classes by their canonical names
+ */
+public class ExcludingClassProvider extends ClassProvider {
+ private final ClassProvider base;
+ private final Predicate<String> testForExclusion;
+
+ public ExcludingClassProvider(ClassProvider base,
+ Predicate<String> testForExclusion) {
+ this.base = base;
+ this.testForExclusion = testForExclusion;
+ }
+
+ @Override
+ public Class<?> getClass(String name) throws ClassNotFoundException {
+ if (!testForExclusion.test(name)) {
+ return base.getClass(name);
+ }
+ // a filtered-out class is the same as non-existing class
+ throw new ClassNotFoundException("Cannot find class " + name);
+ }
+
+ @Override
+ public Stream<Class<?>> getAllClasses() {
+ return base.getAllClasses()
+ .filter(clazz -> !testForExclusion.test(clazz.getCanonicalName()));
+ }
+}
diff --git a/tests/signature/src/android/signature/cts/FailureType.java b/tests/signature/src/android/signature/cts/FailureType.java
index 77820eb..6f538f4 100644
--- a/tests/signature/src/android/signature/cts/FailureType.java
+++ b/tests/signature/src/android/signature/cts/FailureType.java
@@ -4,6 +4,7 @@
* Define the type of the signature check failures.
*/
public enum FailureType {
+ MISSING_ANNOTATION,
MISSING_CLASS,
MISSING_INTERFACE,
MISSING_METHOD,
@@ -14,5 +15,9 @@
MISMATCH_METHOD,
MISMATCH_FIELD,
UNEXPECTED_CLASS,
+ EXTRA_CLASS,
+ EXTRA_INTERFACE,
+ EXTRA_METHOD,
+ EXTRA_FIELD,
CAUGHT_EXCEPTION,
}
diff --git a/tests/signature/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
index df91ddb..0718393 100644
--- a/tests/signature/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
@@ -66,7 +66,7 @@
return mPackageName;
}
- String getShortClassName() {
+ public String getShortClassName() {
return mShortClassName;
}
diff --git a/tests/signature/src/android/signature/cts/ReflectionHelper.java b/tests/signature/src/android/signature/cts/ReflectionHelper.java
index 54640a3..dc2ef5a 100644
--- a/tests/signature/src/android/signature/cts/ReflectionHelper.java
+++ b/tests/signature/src/android/signature/cts/ReflectionHelper.java
@@ -15,8 +15,14 @@
*/
package android.signature.cts;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -24,7 +30,10 @@
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Uses reflection to obtain runtime representations of elements in the API.
@@ -42,7 +51,7 @@
* @return the reflected class, or null if not found.
*/
@SuppressWarnings("unchecked")
- public static Class<?> findMatchingClass(JDiffClassDescription classDescription)
+ public static Class<?> findMatchingClass(JDiffClassDescription classDescription, ClassProvider classProvider)
throws ClassNotFoundException {
// even if there are no . in the string, split will return an
// array of length 1
@@ -51,8 +60,7 @@
String packageName = classDescription.getPackageName();
String currentName = packageName + "." + classNameParts[0];
- Class<?> clz = Class.forName(
- currentName, false, ReflectionHelper.class.getClassLoader());
+ Class<?> clz = classProvider.getClass(currentName);
String absoluteClassName = classDescription.getAbsoluteClassName();
if (clz.getCanonicalName().equals(absoluteClassName)) {
return clz;
@@ -330,4 +338,138 @@
throw new RuntimeException("Got an unknown java.lang.Type");
}
}
+
+ /**
+ * Returns a Class representing an annotation type of the given name.
+ */
+ @SuppressWarnings("unchecked")
+ public static Class<? extends Annotation> getAnnotationClass(String name) {
+ try {
+ Class<?> clazz = Class.forName(
+ name, false, ReflectionHelper.class.getClassLoader());
+ if (clazz.isAnnotation()) {
+ return (Class<? extends Annotation>) clazz;
+ } else {
+ return null;
+ }
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of constructors which are annotated with the given annotation class.
+ */
+ public static Set<Constructor<?>> getAnnotatedConstructors(Class<?> clazz,
+ Class<? extends Annotation> annotation) {
+ Set<Constructor<?>> result = new HashSet<>();
+ if (annotation != null) {
+ for (Constructor<?> c : clazz.getDeclaredConstructors()) {
+ if (c.isAnnotationPresent(annotation)) {
+ // TODO(b/71630695): currently, some API members are not annotated, because
+ // a member is automatically added to the API set if it is in a class with
+ // annotation and it is not @hide. <member>.getDeclaringClass().
+ // isAnnotationPresent(annotationClass) won't help because it will then
+ // incorrectly include non-API members which are marked as @hide;
+ // @hide isn't visible at runtime. Until the issue is fixed, we should
+ // omit those automatically added API members from the test.
+ result.add(c);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a list of methods which are annotated with the given annotation class.
+ */
+ public static Set<Method> getAnnotatedMethods(Class<?> clazz,
+ Class<? extends Annotation> annotation) {
+ Set<Method> result = new HashSet<>();
+ if (annotation != null) {
+ for (Method m : clazz.getDeclaredMethods()) {
+ if (m.isAnnotationPresent(annotation)) {
+ // TODO(b/71630695): see getAnnotatedConstructors for details
+ result.add(m);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a list of fields which are annotated with the given annotation class.
+ */
+ public static Set<Field> getAnnotatedFields(Class<?> clazz,
+ Class<? extends Annotation> annotation) {
+ Set<Field> result = new HashSet<>();
+ if (annotation != null) {
+ for (Field f : clazz.getDeclaredFields()) {
+ if (f.isAnnotationPresent(annotation)) {
+ // TODO(b/71630695): see getAnnotatedConstructors for details
+ result.add(f);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static boolean isInAnnotatedClass(Member m,
+ Class<? extends Annotation> annotationClass) {
+ Class<?> clazz = m.getDeclaringClass();
+ do {
+ if (clazz.isAnnotationPresent(annotationClass)) {
+ return true;
+ }
+ } while ((clazz = clazz.getDeclaringClass()) != null);
+ return false;
+ }
+
+ public static boolean isAnnotatedOrInAnnotatedClass(Field field,
+ Class<? extends Annotation> annotationClass) {
+ if (annotationClass == null) {
+ return true;
+ }
+ return field.isAnnotationPresent(annotationClass)
+ || isInAnnotatedClass(field, annotationClass);
+ }
+
+ public static boolean isAnnotatedOrInAnnotatedClass(Constructor<?> constructor,
+ Class<? extends Annotation> annotationClass) {
+ if (annotationClass == null) {
+ return true;
+ }
+ return constructor.isAnnotationPresent(annotationClass)
+ || isInAnnotatedClass(constructor, annotationClass);
+ }
+
+ public static boolean isAnnotatedOrInAnnotatedClass(Method method,
+ Class<? extends Annotation> annotationClass) {
+ if (annotationClass == null) {
+ return true;
+ }
+ return method.isAnnotationPresent(annotationClass)
+ || isInAnnotatedClass(method, annotationClass);
+ }
+
+ public static boolean isOverridingAnnotatedMethod(Method method,
+ Class<? extends Annotation> annotationClass) {
+ Class<?> clazz = method.getDeclaringClass();
+ while (!(clazz = clazz.getSuperclass()).equals(Object.class)) {
+ try {
+ Method overriddenMethod;
+ overriddenMethod = clazz.getDeclaredMethod(method.getName(),
+ method.getParameterTypes());
+ if (overriddenMethod != null) {
+ return isAnnotatedOrInAnnotatedClass(overriddenMethod, annotationClass);
+ }
+ } catch (NoSuchMethodException e) {
+ continue;
+ } catch (SecurityException e) {
+ throw new RuntimeException(
+ "Error while searching for overridden method. " + method.toString(), e);
+ }
+ }
+ return false;
+ }
}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
index 71d2e37..abdef7b 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
@@ -16,19 +16,23 @@
package android.signature.cts.tests;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
import android.signature.cts.ApiComplianceChecker;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.ExcludingClassProvider;
import android.signature.cts.FailureType;
import android.signature.cts.JDiffClassDescription;
import android.signature.cts.ResultObserver;
-
+import android.signature.cts.tests.data.ApiAnnotation;
import junit.framework.Assert;
import junit.framework.TestCase;
-import java.lang.reflect.Modifier;
-
/**
* Test class for JDiffClassDescription.
*/
+@SuppressWarnings("deprecation")
public class ApiComplianceCheckerTest extends TestCase {
private static final String VALUE = "VALUE";
@@ -36,7 +40,8 @@
private class NoFailures implements ResultObserver {
@Override
public void notifyFailure(FailureType type, String name, String errmsg) {
- Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type);
+ Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type
+ + " error message: " + errmsg);
}
}
@@ -68,14 +73,38 @@
}
private void checkSignatureCompliance(JDiffClassDescription classDescription) {
+ checkSignatureCompliance(classDescription, false);
+ }
+
+ private void checkSignatureCompliance(JDiffClassDescription classDescription,
+ boolean doExactMatch, String... excludedRuntimeClassNames) {
ResultObserver resultObserver = new NoFailures();
- checkSignatureCompliance(classDescription, resultObserver);
+ checkSignatureCompliance(classDescription, resultObserver, doExactMatch,
+ excludedRuntimeClassNames);
}
private void checkSignatureCompliance(JDiffClassDescription classDescription,
ResultObserver resultObserver) {
- ApiComplianceChecker complianceChecker = new ApiComplianceChecker(resultObserver);
+ checkSignatureCompliance(classDescription, resultObserver, false);
+ }
+
+ private void checkSignatureCompliance(JDiffClassDescription classDescription,
+ ResultObserver resultObserver, boolean doExactMatch, String... excludedRuntimeClasses) {
+ ClassProvider provider = new TestClassesProvider();
+ if (excludedRuntimeClasses.length != 0) {
+ provider = new ExcludingClassProvider(provider,
+ name -> Arrays.stream(excludedRuntimeClasses)
+ .anyMatch(myname -> myname.equals(name)));
+ }
+ ApiComplianceChecker complianceChecker = new ApiComplianceChecker(resultObserver,
+ provider);
+ if (doExactMatch) {
+ complianceChecker.setAnnotationForExactMatch(ApiAnnotation.class.getName());
+ }
complianceChecker.checkSignatureCompliance(classDescription);
+ if (doExactMatch) {
+ complianceChecker.checkExactMatch();
+ }
}
/**
@@ -501,4 +530,274 @@
checkSignatureCompliance(clz, observer);
observer.validate();
}
+
+ private static JDiffClassDescription createClass(String name) {
+ JDiffClassDescription clz = new JDiffClassDescription(
+ "android.signature.cts.tests.data", name);
+ clz.setType(JDiffClassDescription.JDiffType.CLASS);
+ clz.setModifier(Modifier.PUBLIC);
+ return clz;
+ }
+
+ private static void addConstructor(JDiffClassDescription clz, String... paramTypes) {
+ JDiffClassDescription.JDiffConstructor constructor = new JDiffClassDescription.JDiffConstructor(
+ clz.getShortClassName(), Modifier.PUBLIC);
+ if (paramTypes != null) {
+ for (String type : paramTypes) {
+ constructor.addParam(type);
+ }
+ }
+ clz.addConstructor(constructor);
+ }
+
+ private static void addPublicVoidMethod(JDiffClassDescription clz, String name) {
+ JDiffClassDescription.JDiffMethod method = new JDiffClassDescription.JDiffMethod(
+ name, Modifier.PUBLIC, "void");
+ clz.addMethod(method);
+ }
+
+ private static void addPublicBooleanField(JDiffClassDescription clz, String name) {
+ JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
+ name, "boolean", Modifier.PUBLIC, VALUE);
+ clz.addField(field);
+ }
+
+ /**
+ * Documented API and runtime classes are exactly matched.
+ */
+ public void testExactApiMatch() {
+ JDiffClassDescription clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+ clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ }
+
+ /**
+ * A constructor is found in the runtime class, but not in the documented API
+ */
+ public void testDetectUnauthorizedConstructorApi() {
+ ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+ JDiffClassDescription clz = createClass("SystemApiClass");
+ // (omitted) addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+
+ observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+ clz = createClass("PublicApiClass");
+ // (omitted) addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+ }
+
+ /**
+ * A method is found in the runtime class, but not in the documented API
+ */
+ public void testDetectUnauthorizedMethodApi() {
+ ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+ JDiffClassDescription clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ // (omitted) addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+
+ observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+
+ clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ // (omitted) addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+ }
+
+ /**
+ * A field is found in the runtime class, but not in the documented API
+ */
+ public void testDetectUnauthorizedFieldApi() {
+ ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_FIELD);
+
+ JDiffClassDescription clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ // (omitted) addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+
+ observer = new ExpectFailure(FailureType.EXTRA_FIELD);
+
+ clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ // (omitted) addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+ }
+
+ /**
+ * A class is found in the runtime classes, but not in the documented API
+ */
+ public void testDetectUnauthorizedClassApi() {
+ ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CLASS);
+ JDiffClassDescription clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.PublicApiClass");
+ // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+ observer.validate();
+
+ observer = new ExpectFailure(FailureType.EXTRA_CLASS);
+
+ clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass");
+ // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+ observer.validate();
+ }
+
+ /**
+ * A member which is declared in an annotated class is currently recognized as an API.
+ */
+ public void testB71630695() {
+ // TODO(b/71630695): currently, some API members are not annotated, because
+ // a member is automatically added to the API set if it is in a class with
+ // annotation and it is not @hide. This should be fixed, but until then,
+ // CTS should respect the existing behavior.
+ JDiffClassDescription clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addConstructor(clz, "float"); // this is not annotated
+
+ checkSignatureCompliance(clz, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+ clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addPublicVoidMethod(clz, "unannotatedApiMethod"); // this is not annotated
+
+ checkSignatureCompliance(clz, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+ clz = createClass("SystemApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addPublicBooleanField(clz, "unannotatedApiField"); // this is not annotated
+
+ checkSignatureCompliance(clz, true,
+ "android.signature.cts.tests.data.PublicApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ }
+
+ /**
+ * An API is documented, but isn't annotated in the runtime class. But, due to b/71630695, this
+ * test can only be done for public API classes.
+ */
+ public void testDetectMissingAnnotation() {
+ ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+
+ JDiffClassDescription clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addConstructor(clz, "int"); // this is not annotated
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+
+ observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+
+ clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addPublicVoidMethod(clz, "privateMethod"); // this is not annotated
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+
+ observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+
+ clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addPublicBooleanField(clz, "privateField"); // this is not annotated
+
+ checkSignatureCompliance(clz, observer, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+ observer.validate();
+ }
+
+ /**
+ * A <code>@hide</code> method should be recognized as API though it is not annotated, if it is
+ * overriding a method which is already an API.
+ */
+ public void testOverriddenHidenMethodIsApi() {
+ JDiffClassDescription clz = createClass("PublicApiClass");
+ addConstructor(clz);
+ addPublicVoidMethod(clz, "apiMethod");
+ addPublicBooleanField(clz, "apiField");
+ addPublicVoidMethod(clz, "anOverriddenMethod"); // not annotated and @hide, but is API
+
+ checkSignatureCompliance(clz, true,
+ "android.signature.cts.tests.data.SystemApiClass",
+ "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+
+ }
}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/TestClassesProvider.java b/tests/signature/tests/src/android/signature/cts/tests/TestClassesProvider.java
new file mode 100644
index 0000000..80176de
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/TestClassesProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests;
+
+import java.util.stream.Stream;
+
+import android.signature.cts.ClassProvider;
+import android.signature.cts.tests.data.AbstractClass;
+import android.signature.cts.tests.data.SystemApiClass;
+import android.signature.cts.tests.data.FinalClass;
+import android.signature.cts.tests.data.PrivateClass;
+import android.signature.cts.tests.data.PublicApiClass;
+import android.signature.cts.tests.data.ForciblyPublicizedPrivateClass;
+import android.signature.cts.tests.data.NormalClass;
+import android.signature.cts.tests.data.NormalException;
+import android.signature.cts.tests.data.NormalInterface;
+import android.signature.cts.tests.data.ApiAnnotation;
+
+public class TestClassesProvider extends ClassProvider {
+ @Override
+ public Stream<Class<?>> getAllClasses() {
+ Stream.Builder<Class<?>> builder = Stream.builder();
+ builder.add(AbstractClass.class);
+ builder.add(FinalClass.class);
+ builder.add(NormalClass.class);
+ builder.add(NormalException.class);
+ builder.add(NormalInterface.class);
+ builder.add(ApiAnnotation.class);
+ builder.add(PublicApiClass.class);
+ builder.add(SystemApiClass.class);
+ builder.add(PrivateClass.class);
+ builder.add(ForciblyPublicizedPrivateClass.class);
+ return builder.build();
+ }
+
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/ApiAnnotation.java b/tests/signature/tests/src/android/signature/cts/tests/data/ApiAnnotation.java
new file mode 100644
index 0000000..17a49b2
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/ApiAnnotation.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests.data;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({
+ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE
+})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiAnnotation {
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/ForciblyPublicizedPrivateClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/ForciblyPublicizedPrivateClass.java
new file mode 100644
index 0000000..1533544
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/ForciblyPublicizedPrivateClass.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests.data;
+
+/**
+ * This mocks a private class, but someone has forcibly added the annotation to make it as an API.
+ */
+@ApiAnnotation
+public class ForciblyPublicizedPrivateClass {
+
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/PrivateClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/PrivateClass.java
new file mode 100644
index 0000000..8f0884c
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/PrivateClass.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests.data;
+
+/**
+ * This mocks an internal private class
+ */
+public class PrivateClass {
+ public void privateMethod() {
+ }
+
+ public boolean privateField;
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClass.java
new file mode 100644
index 0000000..e27d070
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClass.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests.data;
+
+/**
+ * A public API class, with some members are marked as system API
+ */
+public class PublicApiClass extends PublicApiClassParent {
+ public PublicApiClass(boolean foo) {
+ }
+
+ public void publicApiMethod() {
+ }
+
+ public boolean publicApiField;
+
+ /** @hide */
+ @ApiAnnotation
+ public PublicApiClass() {
+ }
+
+ /** @hide */
+ @ApiAnnotation
+ public void apiMethod() {
+ }
+
+ /** @hide */
+ @ApiAnnotation
+ public boolean apiField;
+
+ /** @hide */
+ public PublicApiClass(int foo) {
+ }
+
+ /** @hide */
+ public void privateMethod() {
+ }
+
+ /** @hide */
+ public boolean privateField;
+
+ /** @hide */
+ @Override
+ public void anOverriddenMethod() {
+ // This is @hide but should be recognized as an API because the exact same method is defined
+ // as API in one of the ancestor classes.
+ }
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClassParent.java b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClassParent.java
new file mode 100644
index 0000000..7e29d03
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/PublicApiClassParent.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests.data;
+
+/**
+ * Parent class of PublicApiClass.
+ */
+public abstract class PublicApiClassParent {
+ public void publicMethod() {
+ }
+
+ /** @hide */
+ @ApiAnnotation
+ public abstract void anOverriddenMethod();
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/SystemApiClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/SystemApiClass.java
new file mode 100644
index 0000000..2636d4d
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/SystemApiClass.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.signature.cts.tests.data;
+
+/**
+ * Not a public API, but a system API.
+ *
+ * @hide
+ */
+@ApiAnnotation
+public class SystemApiClass {
+ @ApiAnnotation
+ public SystemApiClass() {
+ }
+
+ @ApiAnnotation
+ public void apiMethod() {
+ }
+
+ @ApiAnnotation
+ public boolean apiField;
+
+ // TODO(b/71630695) this shouldn't be recognized
+ // as an API, as it is not annotated.
+ public SystemApiClass(float foo) {
+
+ }
+
+ // TODO(b/71630695) this shouldn't be recognized
+ // as an API, as it is not annotated.
+ public void unannotatedApiMethod() {
+ }
+
+ // TODO(b/71630695) this shouldn't be recognized
+ // as an API, as it is not annotated.
+ public boolean unannotatedApiField;
+
+ /** @hide */
+ public SystemApiClass(boolean foo) {
+ }
+
+ /** @hide */
+ public void privateMethod() {
+ }
+
+ /** @hide */
+ public boolean privateField;
+}
diff --git a/tests/simplecpu/AndroidTest.xml b/tests/simplecpu/AndroidTest.xml
index 9192e8b..916da50 100644
--- a/tests/simplecpu/AndroidTest.xml
+++ b/tests/simplecpu/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CPU test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/accounts/Android.mk b/tests/tests/accounts/Android.mk
index f5ac4fa..18ab37f 100644
--- a/tests/tests/accounts/Android.mk
+++ b/tests/tests/accounts/Android.mk
@@ -22,9 +22,9 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_STATIC_JAVA_LIBRARIES := \
- CtsAccountTestsCommon ctstestrunner legacy-android-test
+ CtsAccountTestsCommon ctstestrunner
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/accounts/AndroidTest.xml b/tests/tests/accounts/AndroidTest.xml
index b6ad758..77118d7 100644
--- a/tests/tests/accounts/AndroidTest.xml
+++ b/tests/tests/accounts/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Accounts test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/alarmclock/Android.mk b/tests/tests/alarmclock/Android.mk
index adfd51f..5d5c4e4 100644
--- a/tests/tests/alarmclock/Android.mk
+++ b/tests/tests/alarmclock/Android.mk
@@ -23,6 +23,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAlarmClockTestCases
diff --git a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
index d469592..8f04d2a 100644
--- a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
+++ b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
@@ -24,8 +24,10 @@
public enum TestcaseType {
DISMISS_ALARM,
+ DISMISS_TIMER,
SET_ALARM,
SET_ALARM_FOR_DISMISSAL,
+ SET_TIMER_FOR_DISMISSAL,
SNOOZE_ALARM,
}
public static final String TESTCASE_TYPE = "Testcase_type";
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
index ede762a..547203e 100644
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
@@ -78,12 +78,21 @@
AlarmClock.ALARM_SEARCH_MODE_NEXT);
break;
+ case DISMISS_TIMER:
+ intent = new Intent(AlarmClock.ACTION_DISMISS_TIMER);
+ break;
+
case SET_ALARM_FOR_DISMISSAL:
case SET_ALARM:
intent = new Intent(AlarmClock.ACTION_SET_ALARM);
intent.putExtra(AlarmClock.EXTRA_HOUR, 14);
break;
+ case SET_TIMER_FOR_DISMISSAL:
+ intent = new Intent(AlarmClock.ACTION_SET_TIMER);
+ intent.putExtra(AlarmClock.EXTRA_LENGTH, 10);
+ break;
+
case SNOOZE_ALARM:
intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
break;
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
index 224c8ab..69f19b9 100644
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
@@ -89,11 +89,19 @@
intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
break;
+ case DISMISS_TIMER:
+ intent = new Intent(AlarmClock.ACTION_DISMISS_TIMER);
+ break;
+
case SET_ALARM:
case SET_ALARM_FOR_DISMISSAL:
intent = new Intent(AlarmClock.ACTION_SET_ALARM);
break;
+ case SET_TIMER_FOR_DISMISSAL:
+ intent = new Intent(AlarmClock.ACTION_SET_TIMER);
+ break;
+
case SNOOZE_ALARM:
intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
break;
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
new file mode 100644
index 0000000..dff8835
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
@@ -0,0 +1,11 @@
+package android.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+
+public class DismissTimerTest extends AlarmClockTestBase {
+
+ public void testAll() throws Exception {
+ assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.SET_TIMER_FOR_DISMISSAL));
+ assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.DISMISS_TIMER));
+ }
+}
diff --git a/tests/tests/animation/Android.mk b/tests/tests/animation/Android.mk
index c625cbe..2311683 100644
--- a/tests/tests/animation/Android.mk
+++ b/tests/tests/animation/Android.mk
@@ -30,8 +30,7 @@
android-common \
compatibility-device-util \
ctstestrunner \
- platform-test-annotations \
- legacy-android-test
+ platform-test-annotations
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/tests/app.usage/Android.mk b/tests/tests/app.usage/Android.mk
index dbef83c..4233c0b 100644
--- a/tests/tests/app.usage/Android.mk
+++ b/tests/tests/app.usage/Android.mk
@@ -28,9 +28,10 @@
android-support-test \
ctstestrunner \
junit \
- legacy-android-test \
ub-uiautomator
+LOCAL_JAVA_LIBRARIES := android.test.base android.test.runner
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
# Tag this module as a cts test artifact
diff --git a/tests/tests/app.usage/AndroidManifest.xml b/tests/tests/app.usage/AndroidManifest.xml
index 462736a..e02e19d 100644
--- a/tests/tests/app.usage/AndroidManifest.xml
+++ b/tests/tests/app.usage/AndroidManifest.xml
@@ -26,7 +26,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <application>
+ <application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".Activities$ActivityOne" />
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 8868c17..cd48c16 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for app.usage Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/app/Android.mk b/tests/tests/app/Android.mk
index bd679a4..4c0e217 100644
--- a/tests/tests/app/Android.mk
+++ b/tests/tests/app/Android.mk
@@ -24,13 +24,12 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
android-support-test \
- junit \
- legacy-android-test
+ junit
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/app/AndroidTest.xml b/tests/tests/app/AndroidTest.xml
index c9ee968..adebf03 100644
--- a/tests/tests/app/AndroidTest.xml
+++ b/tests/tests/app/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for app Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/appwidget/Android.mk b/tests/tests/appwidget/Android.mk
index 5df6046..0857835 100644
--- a/tests/tests/appwidget/Android.mk
+++ b/tests/tests/appwidget/Android.mk
@@ -28,8 +28,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-target-minus-junit4 \
ctstestrunner \
- junit \
- legacy-android-test
+ junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/appwidget/AndroidManifest.xml b/tests/tests/appwidget/AndroidManifest.xml
index 6f7d053..e7e7944 100644
--- a/tests/tests/appwidget/AndroidManifest.xml
+++ b/tests/tests/appwidget/AndroidManifest.xml
@@ -38,6 +38,30 @@
android:resource="@xml/second_appwidget_info" />
</receiver>
+ <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider1" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_info_with_feature1" />
+ </receiver>
+
+ <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider2" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_info_with_feature2" />
+ </receiver>
+
+ <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider3" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/appwidget_info_with_feature3" />
+ </receiver>
+
<service android:name="android.appwidget.cts.service.MyAppWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS">
</service>
diff --git a/tests/tests/appwidget/AndroidTest.xml b/tests/tests/appwidget/AndroidTest.xml
index f1d2df2..cc4fe42 100644
--- a/tests/tests/appwidget/AndroidTest.xml
+++ b/tests/tests/appwidget/AndroidTest.xml
@@ -13,6 +13,7 @@
limitations under the License.
-->
<configuration description="Config for CTS App Widget test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
@@ -21,6 +22,26 @@
<option name="test-file-name" value="CtsAppWidgetLauncher3.apk" />
<option name="test-file-name" value="CtsAppWidgetTestCases.apk" />
</target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/widgetprovider/" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts/widgetprovider"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsAppWidgetProvider1.apk->/data/local/tmp/cts/widgetprovider/CtsAppWidgetProvider1.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsAppWidgetProvider2.apk->/data/local/tmp/cts/widgetprovider/CtsAppWidgetProvider2.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsAppWidgetProvider3.apk->/data/local/tmp/cts/widgetprovider/CtsAppWidgetProvider3.apk" />
+ </target_preparer>
+
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.appwidget.cts" />
<option name="runtime-hint" value="9m30s" />
diff --git a/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java b/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java
index 954a81b..786b2eb 100644
--- a/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java
+++ b/tests/tests/appwidget/common/src/android/appwidget/cts/common/Constants.java
@@ -27,4 +27,6 @@
public static final String EXTRA_PACKAGE = "PACKAGE_NAME";
public static final String EXTRA_REQUEST = "REQUEST";
+ public static final String ACTION_APPLY_OVERRIDE =
+ "android.appwidget.cts.widgetprovider.APPLY_OVERRIDE";
}
diff --git a/tests/tests/appwidget/packages/src/android/appwidget/cts/packages/SimpleProvider.java b/tests/tests/appwidget/packages/src/android/appwidget/cts/packages/SimpleProvider.java
new file mode 100644
index 0000000..3c29746
--- /dev/null
+++ b/tests/tests/appwidget/packages/src/android/appwidget/cts/packages/SimpleProvider.java
@@ -0,0 +1,25 @@
+package android.appwidget.cts.packages;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.appwidget.cts.common.Constants;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+public class SimpleProvider extends AppWidgetProvider {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+
+ if (Constants.ACTION_APPLY_OVERRIDE.equals(intent.getAction())) {
+ String request = intent.getStringExtra(Constants.EXTRA_REQUEST);
+ AppWidgetManager.getInstance(context).updateAppWidgetProviderInfo(
+ new ComponentName(context, SimpleProvider.class),
+ request);
+ setResultCode(Activity.RESULT_OK);
+ }
+ }
+}
diff --git a/tests/tests/appwidget/packages/widgetprovider/Android.mk b/tests/tests/appwidget/packages/widgetprovider/Android.mk
new file mode 100644
index 0000000..733a4a8
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/Android.mk
@@ -0,0 +1,78 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetProvider1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src) \
+ $(call all-java-files-under, ../../common/src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+ $(LOCAL_PATH)/AndroidManifestV1.xml
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetProvider2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src) \
+ $(call all-java-files-under, ../../common/src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+ $(LOCAL_PATH)/AndroidManifestV2.xml
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetProvider3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src) \
+ $(call all-java-files-under, ../../common/src)
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+ $(LOCAL_PATH)/AndroidManifestV3.xml
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifest.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifest.xml
new file mode 100644
index 0000000..77dfc5b
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest package="android.appwidget.cts.widgetprovider" >
+
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
new file mode 100644
index 0000000..ea23176
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/widget_no_config" />
+ <meta-data android:name="my_custom_info"
+ android:resource="@xml/widget_config" />
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
new file mode 100644
index 0000000..e46e160
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/widget_no_config" />
+ <meta-data android:name="my_custom_info"
+ android:resource="@xml/widget_config_no_resize" />
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
new file mode 100644
index 0000000..6da740d
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+ <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/widget_no_config" />
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/layout/simple_widget.xml b/tests/tests/appwidget/packages/widgetprovider/res/layout/simple_widget.xml
new file mode 100644
index 0000000..b9db450
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/layout/simple_widget.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#ff333333"
+ android:elevation="3dp" />
\ No newline at end of file
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config.xml b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config.xml
new file mode 100644
index 0000000..f45c476
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/simple_widget"
+ android:resizeMode="horizontal|vertical"
+ android:configure="android.appwidget.cts.widgetprovider.ExampleAppWidgetConfigure"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config_no_resize.xml b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config_no_resize.xml
new file mode 100644
index 0000000..428a780
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_config_no_resize.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/simple_widget"
+ android:resizeMode="none"
+ android:configure="android.appwidget.cts.widgetprovider.ExampleAppWidgetConfigure"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_no_config.xml b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_no_config.xml
new file mode 100644
index 0000000..0dacd1c
--- /dev/null
+++ b/tests/tests/appwidget/packages/widgetprovider/res/xml/widget_no_config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<appwidget-provider
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="180dp"
+ android:minHeight="110dp"
+ android:updatePeriodMillis="86400000"
+ android:initialLayout="@layout/simple_widget"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/tests/appwidget/res/xml/appwidget_info_with_feature1.xml b/tests/tests/appwidget/res/xml/appwidget_info_with_feature1.xml
new file mode 100644
index 0000000..cadb283
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/appwidget_info_with_feature1.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="@dimen/first_min_appwidget_size"
+ android:minHeight="@dimen/first_min_appwidget_size"
+ android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+ android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+ android:updatePeriodMillis="@integer/first_update_period_millis"
+ android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen|keyguard"
+ android:widgetFeatures="reconfigurable"
+ android:initialLayout="@layout/first_initial_layout"
+ android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+ android:previewImage="@drawable/first_android_icon"
+ android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/res/xml/appwidget_info_with_feature2.xml b/tests/tests/appwidget/res/xml/appwidget_info_with_feature2.xml
new file mode 100644
index 0000000..89d60eb
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/appwidget_info_with_feature2.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="@dimen/first_min_appwidget_size"
+ android:minHeight="@dimen/first_min_appwidget_size"
+ android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+ android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+ android:updatePeriodMillis="@integer/first_update_period_millis"
+ android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen|keyguard"
+ android:widgetFeatures="hide_from_picker"
+ android:initialLayout="@layout/first_initial_layout"
+ android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+ android:previewImage="@drawable/first_android_icon"
+ android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/res/xml/appwidget_info_with_feature3.xml b/tests/tests/appwidget/res/xml/appwidget_info_with_feature3.xml
new file mode 100644
index 0000000..25936ab
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/appwidget_info_with_feature3.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="@dimen/first_min_appwidget_size"
+ android:minHeight="@dimen/first_min_appwidget_size"
+ android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+ android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+ android:updatePeriodMillis="@integer/first_update_period_millis"
+ android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen|keyguard"
+ android:widgetFeatures="reconfigurable|hide_from_picker"
+ android:initialLayout="@layout/first_initial_layout"
+ android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+ android:previewImage="@drawable/first_android_icon"
+ android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 747a8bd..c0870f2 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -33,6 +33,7 @@
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.appwidget.cts.provider.AppWidgetProviderCallbacks;
+import android.appwidget.cts.provider.AppWidgetProviderWithFeatures;
import android.appwidget.cts.provider.FirstAppWidgetProvider;
import android.appwidget.cts.provider.SecondAppWidgetProvider;
import android.appwidget.cts.service.MyAppWidgetService;
@@ -85,10 +86,6 @@
public void testGetAppInstalledProvidersForCurrentUserLegacy() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// By default we should get only providers for the current user.
List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
@@ -97,10 +94,6 @@
}
public void testGetAppInstalledProvidersForCurrentUserNewCurrentProfile() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We ask only for providers for the current user.
List<AppWidgetProviderInfo> providers = getAppWidgetManager()
.getInstalledProvidersForProfile(Process.myUserHandle());
@@ -110,10 +103,6 @@
}
public void testGetAppInstalledProvidersForCurrentUserNewAllProfiles() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We ask only for providers for all current user's profiles
UserManager userManager = (UserManager) getInstrumentation()
.getTargetContext().getSystemService(Context.USER_SERVICE);
@@ -134,10 +123,6 @@
}
public void testBindAppWidget() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// Create a host and start listening.
AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
host.deleteHost();
@@ -175,9 +160,6 @@
}
public void testGetAppWidgetIdsForHost() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
AppWidgetHost host1 = new AppWidgetHost(getInstrumentation().getTargetContext(), 1);
AppWidgetHost host2 = new AppWidgetHost(getInstrumentation().getTargetContext(), 2);
@@ -207,10 +189,6 @@
}
public void testAppWidgetProviderCallbacks() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
AtomicInteger invocationCounter = new AtomicInteger();
// Set a mock to intercept provider callbacks.
@@ -318,10 +296,6 @@
}
public void testTwoAppWidgetProviderCallbacks() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
AtomicInteger invocationCounter = new AtomicInteger();
// Set a mock to intercept first provider callbacks.
@@ -413,10 +387,6 @@
}
public void testGetAppWidgetIdsForProvider() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -464,10 +434,6 @@
}
public void testGetAppWidgetInfo() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -520,10 +486,6 @@
}
public void testGetAppWidgetOptions() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -569,10 +531,6 @@
}
public void testDeleteHost() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -614,10 +572,6 @@
}
public void testDeleteHosts() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -674,10 +628,6 @@
}
public void testOnProvidersChanged() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -740,10 +690,6 @@
}
public void testUpdateAppWidgetViaComponentName() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -834,10 +780,6 @@
}
public void testUpdateAppWidgetViaWidgetId() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -908,10 +850,6 @@
}
public void testUpdateAppWidgetViaWidgetIds() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -1004,10 +942,6 @@
}
public void testPartiallyUpdateAppWidgetViaWidgetId() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -1082,10 +1016,6 @@
}
public void testPartiallyUpdateAppWidgetViaWidgetIds() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -1196,10 +1126,6 @@
}
public void testCollectionWidgets() throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
-
// We want to bind widgets.
grantBindAppWidgetPermission();
@@ -1308,6 +1234,22 @@
}
}
+ public void testWidgetFeaturesParsed() throws Exception {
+ assertEquals(0, getFirstAppWidgetProviderInfo().widgetFeatures);
+ String packageName = getInstrumentation().getTargetContext().getPackageName();
+
+ assertEquals(AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE,
+ getProviderInfo(new ComponentName(packageName,
+ AppWidgetProviderWithFeatures.Provider1.class.getName())).widgetFeatures);
+ assertEquals(AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER,
+ getProviderInfo(new ComponentName(packageName,
+ AppWidgetProviderWithFeatures.Provider2.class.getName())).widgetFeatures);
+ assertEquals(AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
+ | AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER,
+ getProviderInfo(new ComponentName(packageName,
+ AppWidgetProviderWithFeatures.Provider3.class.getName())).widgetFeatures);
+ }
+
private void waitForCallCount(AtomicInteger counter, int expectedCount) {
synchronized (mLock) {
final long startTimeMillis = SystemClock.uptimeMillis();
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
index fb0dbd1..b98973a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
@@ -16,6 +16,8 @@
package android.appwidget.cts;
+import static org.junit.Assume.assumeTrue;
+
import android.appwidget.AppWidgetProviderInfo;
import android.appwidget.cts.provider.FirstAppWidgetProvider;
import android.appwidget.cts.provider.SecondAppWidgetProvider;
@@ -36,6 +38,12 @@
private static final String SECOND_APP_WIDGET_CONFIGURE_ACTIVITY =
"android.appwidget.cts.provider.SecondAppWidgetConfigureActivity";
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assumeTrue(hasAppWidgets());
+ }
+
public boolean hasAppWidgets() {
return getInstrumentation().getTargetContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS);
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index 019fb66..4f6c657 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -52,9 +52,6 @@
}
private void runPinWidgetTest(final String launcherPkg) throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
setLauncher(launcherPkg + "/" + LAUNCHER_CLASS);
Context context = getInstrumentation().getContext();
@@ -109,9 +106,6 @@
public void verifyIsRequestPinAppWidgetSupported(String launcherPkg, boolean expectedSupport)
throws Exception {
- if (!hasAppWidgets()) {
- return;
- }
setLauncher(launcherPkg + "/" + LAUNCHER_CLASS);
Context context = getInstrumentation().getContext();
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
new file mode 100644
index 0000000..2ec8cf7
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.cts.common.Constants;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Process;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class UpdateProviderInfoTest extends AppWidgetTestCase {
+
+ private static final String PROVIDER_PACKAGE = "android.appwidget.cts.widgetprovider";
+ private static final String PROVIDER_CLASS = "android.appwidget.cts.packages.SimpleProvider";
+
+ private static final String APK_PATH = "data/local/tmp/cts/widgetprovider/";
+ private static final String APK_V1 = APK_PATH + "CtsAppWidgetProvider1.apk";
+ private static final String APK_V2 = APK_PATH + "CtsAppWidgetProvider2.apk";
+ private static final String APK_V3 = APK_PATH + "CtsAppWidgetProvider3.apk";
+
+ private static final String EXTRA_CUSTOM_INFO = "my_custom_info";
+
+ private static final int HOST_ID = 42;
+
+ private static final int RETRY_COUNT = 3;
+
+ private CountDownLatch mProviderChangeNotifier;
+ AppWidgetHost mHost;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ uninstallProvider();
+ createHost();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ uninstallProvider();
+
+ if (mHost != null) {
+ mHost.deleteHost();
+ }
+ }
+
+ public void testInfoOverrides() throws Throwable {
+ // On first install the provider does not have any config activity.
+ installApk(APK_V1);
+ assertNull(getProviderInfo().configure);
+
+ // The provider info is updated
+ updateInfo(EXTRA_CUSTOM_INFO);
+ assertNotNull(getProviderInfo().configure);
+
+ // The provider info is updated
+ updateInfo(null);
+ assertNull(getProviderInfo().configure);
+ }
+
+ public void testOverridesPersistedOnUpdate() throws Exception {
+ installApk(APK_V1);
+ assertNull(getProviderInfo().configure);
+
+ updateInfo(EXTRA_CUSTOM_INFO);
+ assertNotNull(getProviderInfo().configure);
+ assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode),
+ AppWidgetProviderInfo.RESIZE_BOTH);
+
+ // Apk updated, the info is also updated
+ installApk(APK_V2);
+ assertNotNull(getProviderInfo().configure);
+ assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode), 0);
+
+ // The provider info is reverted
+ updateInfo(null);
+ assertNull(getProviderInfo().configure);
+ }
+
+ public void testOverrideClearedWhenMissingInfo() throws Exception {
+ installApk(APK_V1);
+ assertNull(getProviderInfo().configure);
+
+ updateInfo(EXTRA_CUSTOM_INFO);
+ assertNotNull(getProviderInfo().configure);
+
+ // V3 does not have the custom info definition
+ installApk(APK_V3);
+ assertNull(getProviderInfo().configure);
+ }
+
+ private void createHost() throws Exception {
+ try {
+ runTestOnUiThread(() -> {
+ mHost = new AppWidgetHost(getInstrumentation().getTargetContext(), HOST_ID) {
+
+ @Override
+ protected void onProvidersChanged() {
+ super.onProvidersChanged();
+
+ if (mProviderChangeNotifier != null) {
+ mProviderChangeNotifier.countDown();
+ }
+ }
+ };
+ mHost.startListening();
+ });
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ private void updateInfo(String key) throws Exception {
+ mProviderChangeNotifier = new CountDownLatch(1);
+ Intent intent = new Intent(Constants.ACTION_APPLY_OVERRIDE)
+ .setComponent(new ComponentName(PROVIDER_PACKAGE, PROVIDER_CLASS))
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(Constants.EXTRA_REQUEST, key);
+ getInstrumentation().getTargetContext().sendBroadcast(intent);
+
+ // Wait until the app widget manager is notified
+ mProviderChangeNotifier.await();
+ }
+
+ private void uninstallProvider() throws Exception {
+ runShellCommand("pm uninstall " + PROVIDER_PACKAGE);
+ }
+
+ private void installApk(String path) throws Exception {
+ mProviderChangeNotifier = new CountDownLatch(1);
+ runShellCommand("pm install -r -d " + path);
+
+ // Wait until the app widget manager is notified
+ mProviderChangeNotifier.await();
+ }
+
+ private AppWidgetProviderInfo getProviderInfo() throws Exception {
+ for (int i = 0; i < RETRY_COUNT; i++) {
+ mProviderChangeNotifier = new CountDownLatch(1);
+ List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(getInstrumentation()
+ .getTargetContext()).getInstalledProvidersForPackage(
+ PROVIDER_PACKAGE, Process.myUserHandle());
+
+ if (providers != null && !providers.isEmpty()) {
+ return providers.get(0);
+ }
+
+ // Sometimes it could take time for the info to appear after the apk is just installed
+ mProviderChangeNotifier.await(2, TimeUnit.SECONDS);
+ }
+ return null;
+ }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderWithFeatures.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderWithFeatures.java
new file mode 100644
index 0000000..d1f848a
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderWithFeatures.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts.provider;
+
+public abstract class AppWidgetProviderWithFeatures extends StubbableAppWidgetProvider {
+ private static final Object sLock = new Object();
+
+ private static AppWidgetProviderCallbacks sCallbacks;
+
+ public static void setCallbacks(AppWidgetProviderCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ @Override
+ protected AppWidgetProviderCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setProvider(this);
+ }
+ return sCallbacks;
+ }
+ }
+
+ public static final class Provider1 extends AppWidgetProviderWithFeatures { }
+
+ public static final class Provider2 extends AppWidgetProviderWithFeatures { }
+
+ public static final class Provider3 extends AppWidgetProviderWithFeatures { }
+}
diff --git a/tests/tests/assist/Android.mk b/tests/tests/assist/Android.mk
index 6aa818e..f9bc56d 100644
--- a/tests/tests/assist/Android.mk
+++ b/tests/tests/assist/Android.mk
@@ -26,6 +26,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := CtsAssistCommon ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsAssistTestCases
diff --git a/tests/tests/assist/AndroidTest.xml b/tests/tests/assist/AndroidTest.xml
index d8f31e7..13b1847 100644
--- a/tests/tests/assist/AndroidTest.xml
+++ b/tests/tests/assist/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Assist test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/background/AndroidTest.xml b/tests/tests/background/AndroidTest.xml
index 33e20ea..c54940c 100644
--- a/tests/tests/background/AndroidTest.xml
+++ b/tests/tests/background/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for background restrictions CTS test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/bionic/AndroidTest.xml b/tests/tests/bionic/AndroidTest.xml
index ce13307..f6d2b09 100644
--- a/tests/tests/bionic/AndroidTest.xml
+++ b/tests/tests/bionic/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Bionic test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="bionic" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/bluetooth/Android.mk b/tests/tests/bluetooth/Android.mk
index 1af4a3f..4c0b04d 100644
--- a/tests/tests/bluetooth/Android.mk
+++ b/tests/tests/bluetooth/Android.mk
@@ -24,8 +24,9 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 634b033..8aabe9c 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -19,7 +19,6 @@
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
-import android.bluetooth.cts.BluetoothScanReceiver;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
@@ -61,8 +60,10 @@
private static final String TAG = "BluetoothLeScanTest";
- private static final int SCAN_DURATION_MILLIS = 5000;
+ private static final int SCAN_DURATION_MILLIS = 10000;
private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 20000;
+ private static final int SCAN_STOP_TIMEOUT = 2000;
+ private static final int ADAPTER_ENABLE_TIMEOUT = 3000;
private CountDownLatch mFlushBatchScanLatch;
private BluetoothAdapter mBluetoothAdapter;
@@ -82,7 +83,7 @@
// Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
// bluetooth state.
mBluetoothAdapter.enable();
- sleep(3000);
+ sleep(ADAPTER_ENABLE_TIMEOUT);
}
mScanner = mBluetoothAdapter.getBluetoothLeScanner();
mLocationOn = TestUtils.isLocationOn(getContext());
@@ -99,6 +100,8 @@
if (!mLocationOn) {
TestUtils.disableLocation(getContext());
}
+ mBluetoothAdapter.disable();
+ sleep(ADAPTER_ENABLE_TIMEOUT);
}
/**
@@ -112,6 +115,7 @@
long scanStartMillis = SystemClock.elapsedRealtime();
Collection<ScanResult> scanResults = scan();
long scanEndMillis = SystemClock.elapsedRealtime();
+ Log.d(TAG, "scan result size:" + scanResults.size());
assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty());
verifyTimestamp(scanResults, scanStartMillis, scanEndMillis);
}
@@ -140,7 +144,7 @@
mScanner.startScan(filters, settings, filterLeScanCallback);
sleep(SCAN_DURATION_MILLIS);
mScanner.stopScan(filterLeScanCallback);
- sleep(1000);
+ sleep(SCAN_STOP_TIMEOUT);
Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
for (ScanResult result : scanResults) {
assertTrue(filter.matches(result));
@@ -178,51 +182,58 @@
return null;
}
- /**
- * Test of opportunistic BLE scans.
- */
- @MediumTest
- public void testOpportunisticScan() {
- if (!isBleSupported()) {
- return;
- }
- ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
- .build();
- BleScanCallback emptyScanCallback = new BleScanCallback();
-
- // No scans are really started with opportunistic scans only.
- mScanner.startScan(Collections.<ScanFilter>emptyList(), opportunisticScanSettings,
- emptyScanCallback);
- sleep(SCAN_DURATION_MILLIS);
- assertTrue(emptyScanCallback.getScanResults().isEmpty());
-
- BleScanCallback regularScanCallback = new BleScanCallback();
- ScanSettings regularScanSettings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
- List<ScanFilter> filters = new ArrayList<>();
- ScanFilter filter = createScanFilter();
- if (filter != null) {
- filters.add(filter);
- } else {
- Log.d(TAG, "no appropriate filter can be set");
- }
- mScanner.startScan(filters, regularScanSettings, regularScanCallback);
- sleep(SCAN_DURATION_MILLIS);
- // With normal BLE scan client, opportunistic scan client will get scan results.
- assertTrue("opportunistic scan results shouldn't be empty",
- !emptyScanCallback.getScanResults().isEmpty());
-
- // No more scan results for opportunistic scan clients once the normal BLE scan clients
- // stops.
- mScanner.stopScan(regularScanCallback);
- // In case we got scan results before scan was completely stopped.
- sleep(1000);
- emptyScanCallback.clear();
- sleep(SCAN_DURATION_MILLIS);
- assertTrue("opportunistic scan shouldn't have scan results",
- emptyScanCallback.getScanResults().isEmpty());
- }
+// /**
+// * Test of opportunistic BLE scans.
+// * Temporarily disable this test because it is interfered by the GmsCore;
+// * it fails when it obtains results from GmsCore explicit scan.
+// * TODO(b/70865144): re-enable this test.
+// */
+// @MediumTest
+// public void testOpportunisticScan() {
+// if (!isBleSupported()) {
+// return;
+// }
+// ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
+// .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
+// .build();
+// BleScanCallback emptyScanCallback = new BleScanCallback();
+// assertTrue("opportunistic scan shouldn't have scan results",
+// emptyScanCallback.getScanResults().isEmpty());
+//
+// // No scans are really started with opportunistic scans only.
+// mScanner.startScan(Collections.<ScanFilter>emptyList(), opportunisticScanSettings,
+// emptyScanCallback);
+// sleep(SCAN_DURATION_MILLIS);
+// Log.d(TAG, "result: " + emptyScanCallback.getScanResults());
+// assertTrue("opportunistic scan shouldn't have scan results",
+// emptyScanCallback.getScanResults().isEmpty());
+//
+// BleScanCallback regularScanCallback = new BleScanCallback();
+// ScanSettings regularScanSettings = new ScanSettings.Builder()
+// .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+// List<ScanFilter> filters = new ArrayList<>();
+// ScanFilter filter = createScanFilter();
+// if (filter != null) {
+// filters.add(filter);
+// } else {
+// Log.d(TAG, "no appropriate filter can be set");
+// }
+// mScanner.startScan(filters, regularScanSettings, regularScanCallback);
+// sleep(SCAN_DURATION_MILLIS);
+// // With normal BLE scan client, opportunistic scan client will get scan results.
+// assertTrue("opportunistic scan results shouldn't be empty",
+// !emptyScanCallback.getScanResults().isEmpty());
+//
+// // No more scan results for opportunistic scan clients once the normal BLE scan clients
+// // stops.
+// mScanner.stopScan(regularScanCallback);
+// // In case we got scan results before scan was completely stopped.
+// sleep(SCAN_STOP_TIMEOUT);
+// emptyScanCallback.clear();
+// sleep(SCAN_DURATION_MILLIS);
+// assertTrue("opportunistic scan shouldn't have scan results",
+// emptyScanCallback.getScanResults().isEmpty());
+// }
/**
* Test case for BLE Batch scan.
@@ -370,7 +381,7 @@
mScanner.startScan(regularLeScanCallback);
sleep(SCAN_DURATION_MILLIS);
mScanner.stopScan(regularLeScanCallback);
- sleep(1000);
+ sleep(SCAN_STOP_TIMEOUT);
return regularLeScanCallback.getScanResults();
}
diff --git a/tests/tests/calendarcommon/Android.mk b/tests/tests/calendarcommon/Android.mk
index 18ae49e..bfd9f26 100644
--- a/tests/tests/calendarcommon/Android.mk
+++ b/tests/tests/calendarcommon/Android.mk
@@ -25,9 +25,9 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/calendarcommon/AndroidManifest.xml b/tests/tests/calendarcommon/AndroidManifest.xml
index 4f21b18..951404a 100644
--- a/tests/tests/calendarcommon/AndroidManifest.xml
+++ b/tests/tests/calendarcommon/AndroidManifest.xml
@@ -30,7 +30,7 @@
android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
- <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15"></uses-sdk>
+ <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="17"></uses-sdk>
</manifest>
diff --git a/tests/tests/calendarcommon/AndroidTest.xml b/tests/tests/calendarcommon/AndroidTest.xml
index 9169b79..76efb9b 100644
--- a/tests/tests/calendarcommon/AndroidTest.xml
+++ b/tests/tests/calendarcommon/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Calendar test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/car/Android.mk b/tests/tests/car/Android.mk
index db12143..07cbd83 100644
--- a/tests/tests/car/Android.mk
+++ b/tests/tests/car/Android.mk
@@ -24,9 +24,9 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
-LOCAL_JAVA_LIBRARIES := android.car
+LOCAL_JAVA_LIBRARIES := android.car android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/carrierapi/Android.mk b/tests/tests/carrierapi/Android.mk
index 954ae14..4969e8e 100644
--- a/tests/tests/carrierapi/Android.mk
+++ b/tests/tests/carrierapi/Android.mk
@@ -25,8 +25,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
compatibility-device-util \
- junit \
- legacy-android-test
+ junit
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -36,5 +35,6 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_JAVA_LIBRARIES += android.test.runner telephony-common
+LOCAL_JAVA_LIBRARIES += android.test.base
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index e46d53a..204d339 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Carrier APIs test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="telecom" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
diff --git a/tests/tests/colormode/Android.mk b/tests/tests/colormode/Android.mk
index a506fa5..d589a4f 100644
--- a/tests/tests/colormode/Android.mk
+++ b/tests/tests/colormode/Android.mk
@@ -24,7 +24,7 @@
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/colormode/AndroidTest.xml b/tests/tests/colormode/AndroidTest.xml
index 9c09038..eb491c2 100644
--- a/tests/tests/colormode/AndroidTest.xml
+++ b/tests/tests/colormode/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Color Mode test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="graphics" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/contactsproviderwipe/Android.mk b/tests/tests/contactsproviderwipe/Android.mk
index d843256..ad06530 100644
--- a/tests/tests/contactsproviderwipe/Android.mk
+++ b/tests/tests/contactsproviderwipe/Android.mk
@@ -28,7 +28,7 @@
ctstestrunner \
ub-uiautomator
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/contactsproviderwipe/AndroidTest.xml b/tests/tests/contactsproviderwipe/AndroidTest.xml
index b12398b..3a03a1e 100644
--- a/tests/tests/contactsproviderwipe/AndroidTest.xml
+++ b/tests/tests/contactsproviderwipe/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Provider test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index 3b64fa1..1fa78d3 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -24,15 +24,15 @@
LOCAL_JNI_SHARED_LIBRARIES := libnativecursorwindow_jni libnativehelper_compat_libc++
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
ctstestrunner \
services.core \
junit \
- legacy-android-test \
- truth-prebuilt
+ truth-prebuilt \
+ accountaccesslib
LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index b191a11..eb6fecb 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.content.cts.permission.TEST_GRANTED" />
<!-- Used for PackageManager test, don't delete this INTERNET permission -->
@@ -288,6 +289,16 @@
android:process=":providerProcess">
</provider>
+ <activity android:name="com.android.cts.content.StubActivity"/>
+
+ <service android:name="com.android.cts.content.SyncService">
+ <intent-filter>
+ <action android:name="android.content.SyncAdapter"/>
+ </intent-filter>
+ <meta-data android:name="android.content.SyncAdapter"
+ android:resource="@xml/accountaccesssyncadapter" />
+ </service>
+
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 5cab0ac..1fd262f 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -31,6 +31,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsContentTestCases.apk" />
+ <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk
new file mode 100644
index 0000000..09523ba
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk
@@ -0,0 +1,42 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ ctstestrunner \
+ ub-uiautomator \
+ compatibility-device-util \
+ accountaccesslib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSyncAccountAccessOtherCertTestCases
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
rename to tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidTest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidTest.xml
new file mode 100644
index 0000000..cc6da02
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS tests of sync adapters with different certs as the authenticator">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsSyncAccountAccessOtherCertTestCases.apk" />
+ <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.cts.content" />
+ <option name="runtime-hint" value="30s" />
+ </test>
+</configuration>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
rename to tests/tests/content/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
new file mode 100644
index 0000000..95bf60c
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.content;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncRequest;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Tests whether a sync adapter can access accounts.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CtsSyncAccountAccessOtherCertTestCases {
+ private static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
+ private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec
+
+ private static final Pattern PERMISSION_REQUESTED = Pattern.compile(
+ "Permission Requested|Permission requested");
+ private static final Pattern ALLOW_SYNC = Pattern.compile("ALLOW|Allow");
+ public static final String TOKEN_TYPE_REMOVE_ACCOUNTS = "TOKEN_TYPE_REMOVE_ACCOUNTS";
+
+ @Rule
+ public final TestRule mFlakyTestRule = new FlakyTestRule(3);
+
+ @Before
+ public void setUp() throws Exception {
+ allowSyncAdapterRunInBackgroundAndDataInBackground();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ disallowSyncAdapterRunInBackgroundAndDataInBackground();
+ }
+
+ @Test
+ public void testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount() throws Exception {
+ assumeTrue(hasDataConnection());
+ assumeTrue(hasNotificationSupport());
+
+ Intent intent = new Intent(getContext(), StubActivity.class);
+ Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+
+ AccountManager accountManager = getContext().getSystemService(AccountManager.class);
+ Bundle result = accountManager.addAccount("com.stub", null, null, null, activity,
+ null, null).getResult();
+
+ Account addedAccount = new Account(
+ result.getString(AccountManager.KEY_ACCOUNT_NAME),
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+ waitForSyncManagerAccountChangeUpdate();
+
+ try {
+ AbstractThreadedSyncAdapter adapter = SyncAdapter.setNewDelegate();
+
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ SyncRequest request = new SyncRequest.Builder()
+ .setSyncAdapter(null, "com.android.cts.stub.provider")
+ .syncOnce()
+ .setExtras(extras)
+ .setExpedited(true)
+ .setManual(true)
+ .build();
+ ContentResolver.requestSync(request);
+
+ verify(adapter, timeout(SYNC_TIMEOUT_MILLIS).times(0)).onPerformSync(any(), any(),
+ any(), any(), any());
+
+ UiDevice uiDevice = getUiDevice();
+ if (isWatch()) {
+ UiObject2 notification = findPermissionNotificationInStream(uiDevice);
+ notification.click();
+ } else {
+ uiDevice.openNotification();
+ uiDevice.wait(Until.hasObject(By.text(PERMISSION_REQUESTED)),
+ UI_TIMEOUT_MILLIS);
+
+ uiDevice.findObject(By.text(PERMISSION_REQUESTED)).click();
+ }
+
+ uiDevice.wait(Until.hasObject(By.text(ALLOW_SYNC)),
+ UI_TIMEOUT_MILLIS);
+
+ uiDevice.findObject(By.text(ALLOW_SYNC)).click();
+
+ ContentResolver.requestSync(request);
+
+ verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(), any(), any(), any(),
+ any());
+ } finally {
+ // Ask the differently signed authenticator to drop all accounts
+ accountManager.getAuthToken(addedAccount, TOKEN_TYPE_REMOVE_ACCOUNTS,
+ null, false, null, null);
+ activity.finish();
+ }
+ }
+
+ private UiObject2 findPermissionNotificationInStream(UiDevice uiDevice) {
+ uiDevice.pressHome();
+ swipeUp(uiDevice);
+ if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
+ return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
+ }
+ for (int i = 0; i < 100; i++) {
+ if (!swipeUp(uiDevice)) {
+ // We have reached the end of the stream and not found the target.
+ break;
+ }
+ if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) {
+ return uiDevice.findObject(By.text(PERMISSION_REQUESTED));
+ }
+ }
+ return null;
+ }
+
+ private boolean swipeUp(UiDevice uiDevice) {
+ int width = uiDevice.getDisplayWidth();
+ int height = uiDevice.getDisplayHeight();
+ return uiDevice.swipe(
+ width / 2 /* startX */,
+ height - 1 /* startY */,
+ width / 2 /* endX */,
+ 1 /* endY */,
+ 50 /* numberOfSteps */);
+ }
+
+ private boolean isWatch() {
+ return (getContext().getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH;
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private UiDevice getUiDevice() {
+ return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ private void waitForSyncManagerAccountChangeUpdate() {
+ // Wait for the sync manager to be notified for the new account.
+ // Unfortunately, there is no way to detect this event, sigh...
+ SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
+ }
+
+ private boolean hasDataConnection() {
+ ConnectivityManager connectivityManager = getContext().getSystemService(
+ ConnectivityManager.class);
+ NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
+ return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
+ }
+
+ private boolean hasNotificationSupport() {
+ final PackageManager manager = getContext().getPackageManager();
+ return !manager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && !manager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
+ }
+
+ private void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
+ // Allow us to run in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd deviceidle whitelist +" + getContext().getPackageName());
+ // Allow us to use data in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
+ }
+
+ private void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
+ // Allow us to run in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd deviceidle whitelist -" + getContext().getPackageName());
+ // Allow us to use data in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
+ }
+}
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/Android.mk b/tests/tests/content/SyncAccountAccessStubs/Android.mk
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/Android.mk
rename to tests/tests/content/SyncAccountAccessStubs/Android.mk
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml
rename to tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/res/xml/authenticator.xml b/tests/tests/content/SyncAccountAccessStubs/res/xml/authenticator.xml
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/res/xml/authenticator.xml
rename to tests/tests/content/SyncAccountAccessStubs/res/xml/authenticator.xml
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
rename to tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
diff --git a/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
rename to tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubProvider.java
diff --git a/tests/tests/content/fonts_readme.txt b/tests/tests/content/fonts_readme.txt
new file mode 100644
index 0000000..f0de576
--- /dev/null
+++ b/tests/tests/content/fonts_readme.txt
@@ -0,0 +1,15 @@
+All fonts included in this project follow the below copyright and licensing:
+
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/tests/tests/content/lib/Android.mk b/tests/tests/content/lib/Android.mk
new file mode 100644
index 0000000..9aaa6ac
--- /dev/null
+++ b/tests/tests/content/lib/Android.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/content/lib/accountaccess/Android.mk b/tests/tests/content/lib/accountaccess/Android.mk
new file mode 100644
index 0000000..559dc90
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := accountaccesslib
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.java
new file mode 100644
index 0000000..2608b97
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ */
+
+package com.android.cts.content;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Rule for running flaky tests that runs the test up to attempt
+ * count and if one run succeeds reports the tests as passing.
+ */
+// TODO: Move this puppy in a common place, so ppl can use it.
+public class FlakyTestRule implements TestRule {
+ private final int mAttemptCount;
+
+ public FlakyTestRule(int attemptCount) {
+ mAttemptCount = attemptCount;
+ }
+
+ @Override
+ public Statement apply(Statement statement, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ Throwable throwable = null;
+ for (int i = 0; i < mAttemptCount; i++) {
+ try {
+ statement.evaluate();
+ return;
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ }
+ throw throwable;
+ };
+ };
+ }
+}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/StubActivity.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/StubActivity.java
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/StubActivity.java
rename to tests/tests/content/lib/accountaccess/src/com.android.cts.content/StubActivity.java
diff --git a/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.java
new file mode 100644
index 0000000..19fb8ee
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.content;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+
+public class SyncAdapter extends AbstractThreadedSyncAdapter {
+ private static final Object sLock = new Object();
+ private static AbstractThreadedSyncAdapter sDelegate;
+
+ public static AbstractThreadedSyncAdapter setNewDelegate() {
+ AbstractThreadedSyncAdapter delegate = mock(AbstractThreadedSyncAdapter.class);
+
+ synchronized (sLock) {
+ sDelegate = delegate;
+ }
+
+ return delegate;
+ }
+
+ public SyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ }
+
+ private AbstractThreadedSyncAdapter getCopyOfDelegate() {
+ synchronized (sLock) {
+ return sDelegate;
+ }
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority,
+ ContentProviderClient provider, SyncResult syncResult) {
+ AbstractThreadedSyncAdapter delegate = getCopyOfDelegate();
+
+ if (delegate != null) {
+ delegate.onPerformSync(account, extras, authority, provider, syncResult);
+ }
+ }
+}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncService.java
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java
rename to tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncService.java
diff --git a/tests/tests/content/res/font/sample_font_collection.ttc b/tests/tests/content/res/font/sample_font_collection.ttc
new file mode 100644
index 0000000..9252f3d
--- /dev/null
+++ b/tests/tests/content/res/font/sample_font_collection.ttc
Binary files differ
diff --git a/tests/tests/content/res/font/sample_ttc_family.xml b/tests/tests/content/res/font/sample_ttc_family.xml
new file mode 100644
index 0000000..ae9b63f
--- /dev/null
+++ b/tests/tests/content/res/font/sample_ttc_family.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:fontStyle="normal" android:font="@font/sample_font_collection" android:ttcIndex="0" />
+ <font android:fontStyle="italic" android:font="@font/sample_font_collection" android:ttcIndex="1" />
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/content/res/font/sample_variation_settings_family1.xml b/tests/tests/content/res/font/sample_variation_settings_family1.xml
new file mode 100644
index 0000000..c719fa9
--- /dev/null
+++ b/tests/tests/content/res/font/sample_variation_settings_family1.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:font="@font/variable_width_dash_font" android:fontVariationSettings="'wdth' 100.0" />
+</font-family>
diff --git a/tests/tests/content/res/font/sample_variation_settings_family2.xml b/tests/tests/content/res/font/sample_variation_settings_family2.xml
new file mode 100644
index 0000000..94d4d79
--- /dev/null
+++ b/tests/tests/content/res/font/sample_variation_settings_family2.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:font="@font/variable_width_dash_font" android:fontVariationSettings="'wdth' 500.0" />
+</font-family>
diff --git a/tests/tests/content/res/font/variable_width_dash_font.ttf b/tests/tests/content/res/font/variable_width_dash_font.ttf
new file mode 100644
index 0000000..f7a256a
--- /dev/null
+++ b/tests/tests/content/res/font/variable_width_dash_font.ttf
Binary files differ
diff --git a/tests/tests/content/res/font/variable_width_dash_font_source.ttx b/tests/tests/content/res/font/variable_width_dash_font_source.ttx
new file mode 100644
index 0000000..401fe0f
--- /dev/null
+++ b/tests/tests/content/res/font/variable_width_dash_font_source.ttx
@@ -0,0 +1,241 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="dash"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x81ee73dd"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Sep 9 08:01:17 2015"/>
+ <modified value="Thu Mar 9 22:22:31 2017"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="0"/>
+ <yMax value="0"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="500"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="2"/>
+ <maxPoints value="0"/>
+ <maxContours value="0"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <maxTwilightPoints value="12"/>
+ <maxStorage value="28"/>
+ <maxFunctionDefs value="119"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="61"/>
+ <maxSizeOfInstructions value="2967"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="97"/>
+ <usLastCharIndex value="97"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="dash" width="500" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x2D" name="dash"/><!-- ASCII DASH -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+ <contour>
+ <pt x="100" y="-500" on="1"/>
+ <pt x="900" y="-500" on="1"/>
+ <pt x="900" y="1000" on="1"/>
+ <pt x="100" y="1000" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dash">
+ <contour>
+ <pt x="0" y="200" on="1"/>
+ <pt x="100" y="200" on="1"/>
+ <pt x="100" y="300" on="1"/>
+ <pt x="0" y="300" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ MultiAxisFont Test
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ MultiAxisFont Test
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ MultiAxisFontTest-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ MultiAxisFont Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ MultiAxisFont Test
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ MultiAxisFontTest-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-200"/>
+ <underlineThickness value="20"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <fvar>
+ <Axis>
+ <AxisTag>wdth</AxisTag>
+ <MinValue>100.0</MinValue>
+ <DefaultValue>100.0</DefaultValue>
+ <MaxValue>500.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <gvar>
+ <glyphVariations glyph="dash">
+ <tuple>
+ <coord axis="wdth" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="400" y="0"/>
+ <delta pt="2" x="400" y="0"/>
+ <delta pt="3" x="0" y="0"/>
+ <delta pt="4" x="-200" y="0"/>
+ <delta pt="5" x="200" y="0"/>
+ </tuple>
+ </glyphVariations>
+ </gvar>
+
+</ttFont>
diff --git a/tests/tests/content/res/mipmap/icon_background.png b/tests/tests/content/res/mipmap/icon_background.png
new file mode 100644
index 0000000..72a065c
--- /dev/null
+++ b/tests/tests/content/res/mipmap/icon_background.png
Binary files differ
diff --git a/tests/tests/content/res/mipmap/icon_recursive.xml b/tests/tests/content/res/mipmap/icon_recursive.xml
new file mode 100644
index 0000000..b44fe19
--- /dev/null
+++ b/tests/tests/content/res/mipmap/icon_recursive.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/icon_background" />
+ <foreground android:drawable="@mipmap/icon_recursive" />
+</adaptive-icon>
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml b/tests/tests/content/res/xml/accountaccesssyncadapter.xml
similarity index 100%
copy from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
copy to tests/tests/content/res/xml/accountaccesssyncadapter.xml
diff --git a/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java b/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java
new file mode 100644
index 0000000..72785b5
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.cts;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncRequest;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.cts.content.FlakyTestRule;
+import com.android.cts.content.StubActivity;
+import com.android.cts.content.SyncAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests whether a sync adapter can access accounts.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccountAccessSameCertTest {
+ private static final long SYNC_TIMEOUT_MILLIS = 20000; // 20 sec
+
+ @Rule
+ public final TestRule mFlakyTestTRule = new FlakyTestRule(3);
+
+ @Before
+ public void setUp() throws Exception {
+ allowSyncAdapterRunInBackgroundAndDataInBackground();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ disallowSyncAdapterRunInBackgroundAndDataInBackground();
+ }
+
+ @Test
+ public void testAccountAccess_sameCertAsAuthenticatorCanSeeAccount() throws Exception {
+ assumeTrue(hasDataConnection());
+ assumeTrue(hasNotificationSupport());
+
+ Intent intent = new Intent(getContext(), StubActivity.class);
+ Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+
+ AccountManager accountManager = getContext().getSystemService(AccountManager.class);
+ Bundle result = accountManager.addAccount("com.stub", null, null, null, activity,
+ null, null).getResult();
+
+ Account addedAccount = new Account(
+ result.getString(AccountManager.KEY_ACCOUNT_NAME),
+ result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+ waitForSyncManagerAccountChangeUpdate();
+
+ try {
+ AbstractThreadedSyncAdapter adapter = SyncAdapter.setNewDelegate();
+
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_PRIORITY, true);
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ SyncRequest request = new SyncRequest.Builder()
+ .setSyncAdapter(null, "com.android.cts.stub.provider")
+ .syncOnce()
+ .setExtras(extras)
+ .setExpedited(true)
+ .setManual(true)
+ .build();
+ ContentResolver.requestSync(request);
+
+ verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(), any(), any(), any(),
+ any());
+ } finally {
+ accountManager.removeAccount(addedAccount, activity, null, null);
+ activity.finish();
+ }
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private void waitForSyncManagerAccountChangeUpdate() {
+ // Wait for the sync manager to be notified for the new account.
+ // Unfortunately, there is no way to detect this event, sigh...
+ SystemClock.sleep(SYNC_TIMEOUT_MILLIS);
+ }
+
+ private boolean hasDataConnection() {
+ ConnectivityManager connectivityManager = getContext().getSystemService(
+ ConnectivityManager.class);
+ NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
+ return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
+ }
+
+ private boolean hasNotificationSupport() {
+ return !getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
+ private void allowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
+ // Allow us to run in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd deviceidle whitelist +" + getContext().getPackageName());
+ // Allow us to use data in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd netpolicy add restrict-background-whitelist " + Process.myUid());
+ }
+
+ private void disallowSyncAdapterRunInBackgroundAndDataInBackground() throws IOException {
+ // Allow us to run in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd deviceidle whitelist -" + getContext().getPackageName());
+ // Allow us to use data in the background
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "cmd netpolicy remove restrict-background-whitelist " + Process.myUid());
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index dbebbc0..2695fac 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -353,4 +353,18 @@
assertDefaultHandlerValidPriority(intent);
}
}
+
+ public void testFingerprintEnrollStart() {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ assertCanBeHandled(new Intent(Settings.ACTION_FINGERPRINT_ENROLL));
+ }
+ }
+
+ public void testPictureInPictureSettings() {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
+ assertCanBeHandled(new Intent(Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS));
+ }
+ }
}
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index 0d9c56d..4d608ab 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -25,6 +25,8 @@
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.os.Handler;
+import android.os.Looper;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
import android.util.Log;
@@ -335,6 +337,21 @@
assertValidFile(longFile);
}
+ public void testMainLooper() throws Exception {
+ final Thread mainThread = Looper.getMainLooper().getThread();
+ final Handler handler = new Handler(mContext.getMainLooper());
+ handler.post(() -> {
+ assertEquals(mainThread, Thread.currentThread());
+ });
+ }
+
+ public void testMainExecutor() throws Exception {
+ final Thread mainThread = Looper.getMainLooper().getThread();
+ mContext.getMainExecutor().execute(() -> {
+ assertEquals(mainThread, Thread.currentThread());
+ });
+ }
+
private void assertValidFile(File file) throws Exception {
Log.d(TAG, "Checking " + file);
assertTrue("Failed to create " + file, file.createNewFile());
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
index 5f48260..871107c 100644
--- a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
@@ -347,11 +347,11 @@
// Test openOrCreateDatabase with null and actual factory
mDatabase = mContextWrapper.openOrCreateDatabase(DATABASE_NAME1,
- ContextWrapper.MODE_PRIVATE, factory);
+ ContextWrapper.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
assertNotNull(mDatabase);
mDatabase.close();
mDatabase = mContextWrapper.openOrCreateDatabase(DATABASE_NAME2,
- ContextWrapper.MODE_PRIVATE, factory);
+ ContextWrapper.MODE_ENABLE_WRITE_AHEAD_LOGGING, factory);
assertNotNull(mDatabase);
mDatabase.close();
@@ -360,7 +360,7 @@
// Test databaseList()
List<String> list = Arrays.asList(mContextWrapper.databaseList());
- assertEquals(4, list.size()); // Each database has a journal
+ assertEquals(2, list.size());
assertTrue("1) database list: " + list, list.contains(DATABASE_NAME1));
assertTrue("2) database list: " + list, list.contains(DATABASE_NAME2));
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index b4fcb31..7c9e538 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -22,6 +22,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.StrictMode;
+import android.os.StrictMode.ViolationInfo;
+import android.os.StrictMode.ViolationLogger;
import android.preference.PreferenceManager;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -32,6 +34,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test {@link SharedPreferences}.
@@ -322,28 +326,27 @@
}
}
- public void testModeMultiProcess() {
+ public void testModeMultiProcess() throws InterruptedException {
// Pre-load it.
mContext.getSharedPreferences("multiprocessTest", 0);
final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
StrictMode.ThreadPolicy diskReadDeath =
- new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build();
+ new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build();
StrictMode.setThreadPolicy(diskReadDeath);
+ final CountDownLatch latch = new CountDownLatch(1);
+ StrictMode.setViolationLogger(info -> latch.countDown());
// This shouldn't hit disk. (it was already pre-loaded above)
mContext.getSharedPreferences("multiprocessTest", 0);
+ boolean triggered = latch.await(1, TimeUnit.SECONDS);
+ assertFalse(triggered);
- boolean didRead = false;
// This SHOULD hit disk. (multi-process flag is set)
- try {
- mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS);
- fail(); // we shouldn't get here.
- } catch (StrictMode.StrictModeViolation e) {
- didRead = true;
- }
- assertTrue(didRead);
+ mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS);
+ triggered = latch.await(1, TimeUnit.SECONDS);
+ assertTrue(triggered);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
index 886b4f5..9e4fc5c 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -63,7 +63,7 @@
private void checkPkgInfoSame(PackageInfo expected, PackageInfo actual) {
assertEquals(expected.packageName, actual.packageName);
- assertEquals(expected.versionCode, actual.versionCode);
+ assertEquals(expected.getLongVersionCode(), actual.getLongVersionCode());
assertEquals(expected.versionName, actual.versionName);
assertEquals(expected.sharedUserId, actual.sharedUserId);
assertEquals(expected.sharedUserLabel, actual.sharedUserLabel);
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 7f5d6ce..6c02845 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -31,6 +31,7 @@
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -324,6 +325,15 @@
}
}
+ public void testGetDrawable_StackOverflowErrorDrawable_mipmap() {
+ try {
+ mResources.getDrawable(R.mipmap.icon_recursive);
+ fail("Failed at testGetDrawable_StackOverflowErrorDrawable_mipmap");
+ } catch (NotFoundException e) {
+ //expected
+ }
+ }
+
public void testGetDrawableForDensity() {
final Drawable ldpi = mResources.getDrawableForDensity(
R.drawable.density_test, DisplayMetrics.DENSITY_LOW);
@@ -788,6 +798,55 @@
assertNotSame(Typeface.DEFAULT, font);
}
+ private Typeface getLargerTypeface(String text, Typeface typeface1, Typeface typeface2) {
+ Paint p1 = new Paint();
+ p1.setTypeface(typeface1);
+ float width1 = p1.measureText(text);
+ Paint p2 = new Paint();
+ p2.setTypeface(typeface2);
+ float width2 = p2.measureText(text);
+
+ if (width1 > width2) {
+ return typeface1;
+ } else if (width1 < width2) {
+ return typeface2;
+ } else {
+ fail("The widths of the text should not be the same");
+ return null;
+ }
+ }
+
+ public void testGetFont_xmlFileWithTtc() {
+ // Here we test that building typefaces by indexing in font collections works correctly.
+ // We want to ensure that the built typefaces correspond to the fonts with the right index.
+ // sample_font_collection.ttc contains two fonts (with indices 0 and 1). The first one has
+ // glyph "a" of 3em width, and all the other glyphs 1em. The second one has glyph "b" of
+ // 3em width, and all the other glyphs 1em. Hence, we can compare the width of these
+ // glyphs to assert that ttc indexing works.
+ Typeface normalFont = mResources.getFont(R.font.sample_ttc_family);
+ assertNotNull(normalFont);
+ Typeface italicFont = Typeface.create(normalFont, Typeface.ITALIC);
+ assertNotNull(italicFont);
+
+ assertEquals(getLargerTypeface("a", normalFont, italicFont), normalFont);
+ assertEquals(getLargerTypeface("b", normalFont, italicFont), italicFont);
+ }
+
+ public void testGetFont_xmlFileWithVariationSettings() {
+ // Here we test that specifying variation settings for fonts in XMLs works.
+ // We build typefaces from two families containing one font each, using the same font
+ // resource, but having different values for the 'wdth' tag. Then we measure the painted
+ // text to ensure that the tag affects the text width. The font resource used supports
+ // the 'wdth' axis for the dash (-) character.
+ Typeface typeface1 = mResources.getFont(R.font.sample_variation_settings_family1);
+ assertNotNull(typeface1);
+ Typeface typeface2 = mResources.getFont(R.font.sample_variation_settings_family2);
+ assertNotNull(typeface2);
+
+ assertNotSame(typeface1, typeface2);
+ assertEquals(getLargerTypeface("-", typeface1, typeface2), typeface2);
+ }
+
public void testGetFont_invalidXmlFile() {
try {
assertNull(mResources.getFont(R.font.invalid_xmlfamily));
diff --git a/tests/tests/database/Android.mk b/tests/tests/database/Android.mk
index 0e24c15..d8c5987 100644
--- a/tests/tests/database/Android.mk
+++ b/tests/tests/database/Android.mk
@@ -26,10 +26,9 @@
ctstestrunner \
ctstestrunner \
ub-uiautomator \
- junit \
- legacy-android-test
+ junit
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/database/AndroidTest.xml b/tests/tests/database/AndroidTest.xml
index 62338f1..bab4a44 100644
--- a/tests/tests/database/AndroidTest.xml
+++ b/tests/tests/database/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Database test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/database/src/android/database/cts/CursorWindowTest.java b/tests/tests/database/src/android/database/cts/CursorWindowTest.java
index 65ce3fd..b0dc266 100644
--- a/tests/tests/database/src/android/database/cts/CursorWindowTest.java
+++ b/tests/tests/database/src/android/database/cts/CursorWindowTest.java
@@ -21,16 +21,31 @@
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteException;
import android.os.Parcel;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
-public class CursorWindowTest extends AndroidTestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CursorWindowTest {
private static final String TEST_STRING = "Test String";
+ @Test
public void testWriteCursorToWindow() throws Exception {
// create cursor
String[] colNames = new String[]{"_id", "name", "number", "profit"};
@@ -75,6 +90,7 @@
assertEquals(0, window.getNumRows());
}
+ @Test
public void testNull() {
CursorWindow window = getOneByOneWindow();
@@ -82,10 +98,11 @@
assertTrue(window.putNull(0, 0));
assertNull(window.getString(0, 0));
assertEquals(0, window.getLong(0, 0));
- assertEquals(0.0, window.getDouble(0, 0));
+ assertEquals(0.0, window.getDouble(0, 0), 0.0);
assertNull(window.getBlob(0, 0));
}
+ @Test
public void testEmptyString() {
CursorWindow window = getOneByOneWindow();
@@ -93,9 +110,10 @@
assertTrue(window.putString("", 0, 0));
assertEquals("", window.getString(0, 0));
assertEquals(0, window.getLong(0, 0));
- assertEquals(0.0, window.getDouble(0, 0));
+ assertEquals(0.0, window.getDouble(0, 0), 0.0);
}
+ @Test
public void testConstructors() {
int TEST_NUMBER = 5;
CursorWindow cursorWindow;
@@ -123,6 +141,7 @@
assertEquals(TEST_STRING, cursorWindow.getString(TEST_NUMBER, 0));
}
+ @Test
public void testDataStructureOperations() {
CursorWindow cursorWindow = new CursorWindow(true);
@@ -175,6 +194,7 @@
}
}
+ @Test
public void testAccessDataValues() {
final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL;
final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER;
@@ -214,8 +234,8 @@
assertEquals(0, cursorWindow.getLong(0, 0));
assertEquals(0, cursorWindow.getInt(0, 0));
assertEquals(0, cursorWindow.getShort(0, 0));
- assertEquals(0.0, cursorWindow.getDouble(0, 0));
- assertEquals(0.0f, cursorWindow.getFloat(0, 0), 0.00000001f);
+ assertEquals(0.0, cursorWindow.getDouble(0, 0), 0.0);
+ assertEquals(0.0f, cursorWindow.getFloat(0, 0), 0.0);
assertFalse(cursorWindow.isNull(0, 0));
assertFalse(cursorWindow.isBlob(0, 0));
@@ -226,8 +246,8 @@
assertEquals(0, cursorWindow.getLong(0, 1));
assertEquals(0, cursorWindow.getInt(0, 1));
assertEquals(0, cursorWindow.getShort(0, 1));
- assertEquals(0.0, cursorWindow.getDouble(0, 1));
- assertEquals(0.0f, cursorWindow.getFloat(0, 1), 0.00000001f);
+ assertEquals(0.0, cursorWindow.getDouble(0, 1), 0.0);
+ assertEquals(0.0f, cursorWindow.getFloat(0, 1), 0.0);
assertNull(cursorWindow.getBlob(0, 1));
assertTrue(cursorWindow.isNull(0, 1));
// If the field is null, isBlob will return true.
@@ -239,8 +259,8 @@
assertEquals(NUMBER_INTEGER, cursorWindow.getInt(0, 2));
assertEquals(Long.toString(NUMBER_LONG_INTEGER), cursorWindow.getString(0, 2));
assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 2));
- assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 2), 0.00000001f);
- assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 2), 0.00000001);
+ assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 2), 0.0);
+ assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 2), 0.0);
try {
cursorWindow.getBlob(0, 2);
fail("Can't get Blob from a Integer value.");
@@ -259,8 +279,8 @@
assertEquals(NUMBER_FLOAT_SCIENCE_STRING2.substring(0, 6), cursorWindow.getString(0, 3)
.substring(0, 6));
assertEquals(NUMBER_SHORT, cursorWindow.getShort(0, 3));
- assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 3), 0.00000001f);
- assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 3), 0.00000001);
+ assertEquals(NUMBER_FLOAT_SCIENCE, cursorWindow.getFloat(0, 3), 0.0);
+ assertEquals(NUMBER_DOUBLE_SCIENCE, cursorWindow.getDouble(0, 3), 0.0);
try {
cursorWindow.getBlob(0, 3);
fail("Can't get Blob from a Double value.");
@@ -279,6 +299,7 @@
assertTrue(cursorWindow.isBlob(0, 4));
}
+ @Test
public void testCopyStringToBuffer() {
int DEFAULT_ARRAY_LENGTH = 64;
String baseString = "0123456789";
@@ -314,6 +335,7 @@
assertEquals(expectedString.length(), charArrayBuffer.data.length);
}
+ @Test
public void testAccessStartPosition() {
final int TEST_POSITION_1 = 0;
final int TEST_POSITION_2 = 3;
@@ -343,6 +365,7 @@
}
}
+ @Test
public void testClearAndOnAllReferencesReleased() {
MockCursorWindow cursorWindow = new MockCursorWindow(true);
@@ -369,11 +392,43 @@
assertTrue(cursorWindow.hasReleasedAllReferences());
}
+ @Test
public void testDescribeContents() {
CursorWindow cursorWindow = new CursorWindow(true);
assertEquals(0, cursorWindow.describeContents());
}
+ @Test
+ public void testDefaultCursorWindowSize() {
+ CursorWindow cursorWindow = new CursorWindow("test");
+ cursorWindow.setNumColumns(1);
+ byte[] bytes = new byte[1024];
+ Arrays.fill(bytes, (byte) 1);
+ // Ensure that the default is not too small and it's possible to fill CursorWindow
+ // with ~2Mb of data
+ int testRowCount = 2016;
+ for (int i = 0; i < testRowCount; i++) {
+ assertTrue(cursorWindow.allocRow());
+ assertTrue("Allocation failed for row " + i, cursorWindow.putBlob(bytes, i, 0));
+ }
+ assertTrue(cursorWindow.allocRow());
+ assertFalse("Allocation should fail for row " + testRowCount,
+ cursorWindow.putBlob(bytes, testRowCount, 0));
+ }
+
+ @Test
+ public void testCustomSize() {
+ // Allocate CursorWindow with max size 10KB and test that restriction is enforced
+ CursorWindow cursorWindow = new CursorWindow("test", 10000);
+ cursorWindow.setNumColumns(1);
+ byte[] bytes = new byte[8000];
+ Arrays.fill(bytes, (byte) 1);
+ assertTrue(cursorWindow.allocRow());
+ assertTrue("Allocation of 1 row should succeed", cursorWindow.putBlob(bytes, 0, 0));
+ assertTrue(cursorWindow.allocRow());
+ assertFalse("Allocation of 2nd row should fail", cursorWindow.putBlob(bytes, 1, 0));
+ }
+
private class MockCursorWindow extends CursorWindow {
private boolean mHasReleasedAllReferences = false;
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java
index a506de5..a11abf6 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteCursorTest.java
@@ -19,10 +19,13 @@
import android.content.Context;
import android.database.AbstractCursor;
+import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
+import android.database.SQLException;
import android.database.StaleDataException;
+import android.database.sqlite.SQLiteBlobTooBigException;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDirectCursorDriver;
@@ -210,6 +213,46 @@
assertEquals(TEST_COUNT - TEST_ARG2, cursor.getCount());
}
+ public void testRowTooBig() {
+ mDatabase.execSQL("CREATE TABLE Tst (Txt BLOB NOT NULL);");
+ byte[] testArr = new byte[10000];
+ Arrays.fill(testArr, (byte) 1);
+ for (int i = 0; i < 10; i++) {
+ mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{testArr});
+ }
+
+ // Now reduce window size, so that no rows can fit
+ Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
+ CursorWindow cw = new CursorWindow("test", 5000);
+ AbstractWindowedCursor ac = (AbstractWindowedCursor) cursor;
+ ac.setWindow(cw);
+
+ try {
+ ac.moveToNext();
+ fail("Exception is expected when row exceeds CursorWindow size");
+ } catch (SQLiteBlobTooBigException expected) {
+ }
+ }
+
+ public void testFillWindowForwardOnly() {
+ mDatabase.execSQL("CREATE TABLE Tst (Num Integer NOT NULL);");
+ mDatabase.beginTransaction();
+ for (int i = 0; i < 100; i++) {
+ mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{i});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
+ SQLiteCursor ac = (SQLiteCursor) cursor;
+ CursorWindow window = new CursorWindow("test", 1000);
+ ac.setFillWindowForwardOnly(true);
+ ac.setWindow(window);
+ assertTrue(ac.moveToFirst());
+ // Now skip 70 rows and check that the window start position corresponds to row 70
+ ac.move(70);
+ assertEquals(70, window.getStartPosition());
+ }
+
public void testOnMove() {
// Do not test this API. It is callback which:
// 1. The callback mechanism has been tested in super class
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
index 26f8794..56a815b 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -33,6 +33,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteGlobal;
import android.database.sqlite.SQLiteQuery;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteTransactionListener;
@@ -1390,6 +1391,16 @@
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
}
+ public void testDisableWriteAheadLogging() {
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ mDatabase.disableWriteAheadLogging();
+ assertFalse(mDatabase.isWriteAheadLoggingEnabled());
+ // Verify that default journal mode is set if WAL is disabled
+ String defaultJournalMode = SQLiteGlobal.getDefaultJournalMode();
+ assertTrue(DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase(defaultJournalMode));
+ }
+
public void testEnableThenDisableWriteAheadLoggingUsingOpenFlag() {
closeAndDeleteDatabase();
mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), null,
@@ -1584,4 +1595,64 @@
} catch (IllegalArgumentException expected) {
}
}
+
+ public void testDefaultJournalModeNotWAL() {
+ String defaultJournalMode = SQLiteGlobal.getDefaultJournalMode();
+ assertFalse("Default journal mode should not be WAL",
+ DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase(defaultJournalMode));
+ }
+
+ public void testCompatibilityWALIsDefaultWhenSupported() {
+ if (!SQLiteGlobal.isCompatibilityWalSupported()) {
+ Log.i(TAG, "Compatibility WAL not supported. "
+ + "Skipping testCompatibilityWALIsDefaultWhenSupported");
+ return;
+ }
+
+ assertTrue("Journal mode should be WAL if compatibility WAL is supported",
+ DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+ .equalsIgnoreCase("WAL"));
+ }
+
+ /**
+ * Test that app can specify journal mode/synchronous mode
+ */
+ public void testJournalModeSynchronousModeOverride() {
+ mDatabase.close();
+ SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .setJournalMode("DELETE").setSynchronousMode("OFF").build();
+ mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile, params);
+
+ String journalMode = DatabaseUtils
+ .stringForQuery(mDatabase, "PRAGMA journal_mode", null);
+
+ assertEquals("DELETE", journalMode.toUpperCase());
+ String syncMode = DatabaseUtils
+ .stringForQuery(mDatabase, "PRAGMA synchronous", null);
+
+ assertEquals("0", syncMode);
+ }
+
+ /**
+ * Test that enableWriteAheadLogging is not affected by app's journal mode/synchronous mode
+ * settings
+ */
+ public void testEnableWalOverridesJournalModeSynchronousMode() {
+ mDatabase.close();
+ SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .setJournalMode("DELETE").setSynchronousMode("OFF").build();
+ mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile, params);
+ mDatabase.enableWriteAheadLogging();
+
+ String journalMode = DatabaseUtils
+ .stringForQuery(mDatabase, "PRAGMA journal_mode", null);
+
+ assertEquals("WAL", journalMode.toUpperCase());
+ String syncMode = DatabaseUtils
+ .stringForQuery(mDatabase, "PRAGMA synchronous", null);
+
+ assertEquals("2" /* FULL */, syncMode);
+ }
+
}
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
index 853f24b..20c9a0d 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.database.Cursor;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
@@ -31,6 +32,9 @@
import android.test.AndroidTestCase;
import android.util.Log;
+import java.io.File;
+import java.util.Arrays;
+
import static android.database.sqlite.cts.DatabaseTestUtils.getDbInfoOutput;
import static android.database.sqlite.cts.DatabaseTestUtils.waitForConnectionToClose;
@@ -55,6 +59,7 @@
@Override
protected void tearDown() throws Exception {
mOpenHelper.close();
+ SQLiteDatabase.deleteDatabase(mContext.getDatabasePath(TEST_DATABASE_NAME));
super.tearDown();
}
@@ -204,17 +209,73 @@
output.contains("Connection #0:"));
}
+ public void testOpenParamsConstructor() {
+ SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .build();
+
+ MockOpenHelper helper = new MockOpenHelper(mContext, null, 1, params);
+ SQLiteDatabase database = helper.getWritableDatabase();
+ assertNotNull(database);
+ helper.close();
+ }
+
+ /**
+ * Tests a scenario in WAL mode with multiple connections, when a connection should see schema
+ * changes made from another connection.
+ */
+ public void testWalSchemaChangeVisibilityOnUpgrade() {
+ File dbPath = mContext.getDatabasePath(TEST_DATABASE_NAME);
+ SQLiteDatabase.deleteDatabase(dbPath);
+ SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
+ db.execSQL("CREATE TABLE test_table (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
+ db.setVersion(1);
+ db.close();
+ mOpenHelper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 2) {
+ {
+ setWriteAheadLoggingEnabled(true);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion == 1) {
+ db.execSQL("ALTER TABLE test_table ADD column2 INT DEFAULT 1234");
+ db.execSQL("CREATE TABLE test_table2 (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
+ }
+ }
+ };
+ // Check if can see the new column
+ try (Cursor cursor = mOpenHelper.getReadableDatabase()
+ .rawQuery("select * from test_table", null)) {
+ assertEquals("Newly added column should be visible. Returned columns: " + Arrays
+ .toString(cursor.getColumnNames()), 2, cursor.getColumnCount());
+ }
+ // Check if can see the new table
+ try (Cursor cursor = mOpenHelper.getReadableDatabase()
+ .rawQuery("select * from test_table2", null)) {
+ assertEquals(1, cursor.getColumnCount());
+ }
+ }
+
private MockOpenHelper getOpenHelper() {
return new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION);
}
- private class MockOpenHelper extends SQLiteOpenHelper {
+ private static class MockOpenHelper extends SQLiteOpenHelper {
private boolean mHasCalledOnOpen = false;
- public MockOpenHelper(Context context, String name, CursorFactory factory, int version) {
+ MockOpenHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
+ MockOpenHelper(Context context, String name, int version,
+ SQLiteDatabase.OpenParams openParams) {
+ super(context, name, version, openParams);
+ }
+
@Override
public void onCreate(SQLiteDatabase db) {
}
@@ -237,8 +298,8 @@
}
}
- private class MockCursor extends SQLiteCursor {
- public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
+ private static class MockCursor extends SQLiteCursor {
+ MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
SQLiteQuery query) {
super(db, driver, editTable, query);
}
diff --git a/tests/tests/debug/Android.mk b/tests/tests/debug/Android.mk
index f1b9b27..c715d07 100644
--- a/tests/tests/debug/Android.mk
+++ b/tests/tests/debug/Android.mk
@@ -30,7 +30,7 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner nativetesthelper
LOCAL_JNI_SHARED_LIBRARIES := libdebugtest
diff --git a/tests/tests/debug/AndroidManifest.xml b/tests/tests/debug/AndroidManifest.xml
index 4b3254a..091e778 100644
--- a/tests/tests/debug/AndroidManifest.xml
+++ b/tests/tests/debug/AndroidManifest.xml
@@ -26,8 +26,6 @@
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.debug.cts"
android:label="CTS tests of native debugging API">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
</instrumentation>
</manifest>
diff --git a/tests/tests/debug/libdebugtest/Android.mk b/tests/tests/debug/libdebugtest/Android.mk
index 80eb256..d3db70f 100644
--- a/tests/tests/debug/libdebugtest/Android.mk
+++ b/tests/tests/debug/libdebugtest/Android.mk
@@ -31,6 +31,7 @@
android_debug_cts.cpp
LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
LOCAL_SDK_VERSION := 23
LOCAL_NDK_STL_VARIANT := c++_static
diff --git a/tests/tests/debug/libdebugtest/android_debug_cts.cpp b/tests/tests/debug/libdebugtest/android_debug_cts.cpp
index fb87a28..3aa4318 100644
--- a/tests/tests/debug/libdebugtest/android_debug_cts.cpp
+++ b/tests/tests/debug/libdebugtest/android_debug_cts.cpp
@@ -15,6 +15,7 @@
*/
#include <jni.h>
+#include <gtest/gtest.h>
#include <android/log.h>
#include <errno.h>
@@ -29,6 +30,7 @@
#define LOG_TAG "Cts-DebugTest"
+// Used by child processes only
#define assert_or_exit(x) \
do { \
if(x) break; \
@@ -36,29 +38,21 @@
errno, strerror(errno)); \
_exit(1); \
} while (0)
-#define assert_or_return(x) \
- do { \
- if(x) break; \
- __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Assertion " #x " failed. errno(%d): %s", \
- errno, strerror(errno)); \
- return false; \
- } while (0)
-static bool parent(pid_t child) {
+static void parent(pid_t child) {
int status;
int wpid = waitpid(child, &status, 0);
- assert_or_return(wpid == child);
- assert_or_return(WIFEXITED(status));
- assert_or_return(WEXITSTATUS(status ) == 0);
- return true;
+ ASSERT_EQ(child, wpid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(0, WEXITSTATUS(status));
}
-static bool run_test(const std::function<void(pid_t)> &test) {
+static void run_test(const std::function<void(pid_t)> &test) {
pid_t pid = fork();
- assert_or_return(pid >= 0);
- if (pid != 0)
- return parent(pid);
- else {
+ ASSERT_NE(-1, pid) << "fork() failed with " << strerror(errno);
+ if (pid != 0) {
+ parent(pid);
+ } else {
// child
test(getppid());
_exit(0);
@@ -75,12 +69,10 @@
assert_or_exit(ptrace(PTRACE_DETACH, parent, nullptr, nullptr) == 0);
}
-// public static native boolean ptraceAttach();
-extern "C" jboolean Java_android_debug_cts_DebugTest_ptraceAttach(JNIEnv *, jclass) {
- return run_test(ptraceAttach);
+TEST(DebugTest, ptraceAttach) {
+ run_test(ptraceAttach);
}
-
static void processVmReadv(pid_t parent, const std::vector<long *> &addresses) {
long destination;
iovec local = { &destination, sizeof destination };
@@ -99,11 +91,11 @@
static long global_variable = 0x47474747;
// public static native boolean processVmReadv();
-extern "C" jboolean Java_android_debug_cts_DebugTest_processVmReadv(JNIEnv *, jclass) {
+TEST(DebugTest, processVmReadv) {
long stack_variable = 0x42424242;
// This runs the test with a selection of different kinds of addresses and
// makes sure the child process (simulating a debugger) can read them.
- return run_test([&](pid_t parent) {
+ run_test([&](pid_t parent) {
processVmReadv(parent, std::vector<long *>{
&global_variable, &stack_variable,
reinterpret_cast<long *>(&processVmReadv)});
@@ -111,9 +103,9 @@
}
// public static native boolean processVmReadvNullptr();
-extern "C" jboolean Java_android_debug_cts_DebugTest_processVmReadvNullptr(JNIEnv *, jclass) {
+TEST(DebugTest, processVmReadvNullptr) {
// Make sure reading unallocated memory behaves reasonably.
- return run_test([](pid_t parent) {
+ run_test([](pid_t parent) {
long destination;
iovec local = {&destination, sizeof destination};
iovec remote = {nullptr, sizeof(long)};
diff --git a/tests/tests/debug/src/android/debug/cts/DebugTest.java b/tests/tests/debug/src/android/debug/cts/DebugTest.java
index ca55d9c..993f02b 100644
--- a/tests/tests/debug/src/android/debug/cts/DebugTest.java
+++ b/tests/tests/debug/src/android/debug/cts/DebugTest.java
@@ -16,26 +16,10 @@
package android.debug.cts;
-import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import com.android.gtestrunner.GtestRunner;
+import com.android.gtestrunner.TargetLibrary;
-public class DebugTest extends TestCase {
-
- static {
- System.loadLibrary("debugtest");
- }
-
- public static native boolean ptraceAttach();
- public void test_ptraceAttach() {
- assertEquals(true, ptraceAttach());
- }
-
- public static native boolean processVmReadv();
- public void test_processVmReadv() {
- assertEquals(true, processVmReadv());
- }
-
- public static native boolean processVmReadvNullptr();
- public void test_processVmReadvNullptr() {
- assertEquals(true, processVmReadvNullptr());
- }
-}
+@RunWith(GtestRunner.class)
+@TargetLibrary("debugtest")
+public class DebugTest {}
diff --git a/tests/tests/display/Android.mk b/tests/tests/display/Android.mk
index 53ae177..a62d0ee 100644
--- a/tests/tests/display/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -27,7 +27,9 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/display/AndroidTest.xml b/tests/tests/display/AndroidTest.xml
index 8b174f1..3e1b38d 100644
--- a/tests/tests/display/AndroidTest.xml
+++ b/tests/tests/display/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Display test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/dpi/Android.mk b/tests/tests/dpi/Android.mk
index 8bb7d64..46deccb 100644
--- a/tests/tests/dpi/Android.mk
+++ b/tests/tests/dpi/Android.mk
@@ -17,7 +17,9 @@
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -41,7 +43,9 @@
# CTS tests, so drop it into a library that other tests can use.
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := src/android/dpi/cts/DefaultManifestAttributesTest.java
diff --git a/tests/tests/dpi/AndroidTest.xml b/tests/tests/dpi/AndroidTest.xml
index e12b36d..5e07b7c 100644
--- a/tests/tests/dpi/AndroidTest.xml
+++ b/tests/tests/dpi/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS DPI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/dpi2/Android.mk b/tests/tests/dpi2/Android.mk
index dcd2c0f..f366781 100644
--- a/tests/tests/dpi2/Android.mk
+++ b/tests/tests/dpi2/Android.mk
@@ -20,7 +20,7 @@
# We use the DefaultManifestAttributesTest from the android.cts.dpi package.
LOCAL_STATIC_JAVA_LIBRARIES := android.cts.dpi ctstestrunner junit
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/dpi2/AndroidManifest.xml b/tests/tests/dpi2/AndroidManifest.xml
index 689be29..f3d5be0 100644
--- a/tests/tests/dpi2/AndroidManifest.xml
+++ b/tests/tests/dpi2/AndroidManifest.xml
@@ -25,7 +25,7 @@
<!-- target cupcake so we can test the default attributes get set
properly for the screen size attributes. -->
- <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="3" />
+ <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="17" />
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.dpi2.cts"
diff --git a/tests/tests/dpi2/AndroidTest.xml b/tests/tests/dpi2/AndroidTest.xml
index 589935d..0c2f6dc 100644
--- a/tests/tests/dpi2/AndroidTest.xml
+++ b/tests/tests/dpi2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS DPI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java b/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
index 04db412..abb8d66 100644
--- a/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
+++ b/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
@@ -34,6 +34,6 @@
// This is a sanity test to make sure that we're instrumenting the proper package
public void testPackageHasExpectedSdkVersion() {
- assertEquals(3, getAppInfo().targetSdkVersion);
+ assertEquals(3, getAppInfo().minSdkVersion);
}
}
diff --git a/tests/tests/dreams/Android.mk b/tests/tests/dreams/Android.mk
index 383c9c3..50c4d7f 100644
--- a/tests/tests/dreams/Android.mk
+++ b/tests/tests/dreams/Android.mk
@@ -24,9 +24,9 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/dreams/AndroidTest.xml b/tests/tests/dreams/AndroidTest.xml
index 7274740..6f77cca 100644
--- a/tests/tests/dreams/AndroidTest.xml
+++ b/tests/tests/dreams/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Dreams test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="sysui" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/drm/Android.mk b/tests/tests/drm/Android.mk
index 13ac8d6..b1f2aac 100644
--- a/tests/tests/drm/Android.mk
+++ b/tests/tests/drm/Android.mk
@@ -24,7 +24,9 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/drm/AndroidTest.xml b/tests/tests/drm/AndroidTest.xml
index 1a9d0b7..996f3f1 100644
--- a/tests/tests/drm/AndroidTest.xml
+++ b/tests/tests/drm/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS DRM test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/dynamic_linker/Android.mk b/tests/tests/dynamic_linker/Android.mk
index 97518bd..ef122ca 100644
--- a/tests/tests/dynamic_linker/Android.mk
+++ b/tests/tests/dynamic_linker/Android.mk
@@ -18,6 +18,7 @@
LOCAL_MODULE := libdynamiclinker_native_lib_a
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := native_lib_a.cpp
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_SDK_VERSION := 25
LOCAL_NDK_STL_VARIANT := c++_static
@@ -28,6 +29,7 @@
LOCAL_MODULE := libdynamiclinker_native_lib_b
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := native_lib_b.cpp
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_SDK_VERSION := 25
LOCAL_NDK_STL_VARIANT := c++_static
@@ -37,7 +39,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, .)
LOCAL_MULTILIB := both
LOCAL_JNI_SHARED_LIBRARIES := libdynamiclinker_native_lib_a libdynamiclinker_native_lib_b
diff --git a/tests/tests/dynamic_linker/AndroidTest.xml b/tests/tests/dynamic_linker/AndroidTest.xml
index 5cc4317..483ae03 100644
--- a/tests/tests/dynamic_linker/AndroidTest.xml
+++ b/tests/tests/dynamic_linker/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS dynamic linker test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="bionic" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/effect/AndroidTest.xml b/tests/tests/effect/AndroidTest.xml
index 072028b..ae32cd5 100644
--- a/tests/tests/effect/AndroidTest.xml
+++ b/tests/tests/effect/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Effect test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/externalservice/Android.mk b/tests/tests/externalservice/Android.mk
index 62afaad..c64c0ca 100644
--- a/tests/tests/externalservice/Android.mk
+++ b/tests/tests/externalservice/Android.mk
@@ -27,6 +27,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := CtsExternalServiceCommon compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
# Tag this module as a cts test artifact
diff --git a/tests/tests/externalservice/service/Android.mk b/tests/tests/externalservice/service/Android.mk
index 9fc0033..8563cb1 100644
--- a/tests/tests/externalservice/service/Android.mk
+++ b/tests/tests/externalservice/service/Android.mk
@@ -21,7 +21,11 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := CtsExternalServiceCommon ctstestrunner compatibility-device-util
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ CtsExternalServiceCommon \
+ ctstestrunner \
+ compatibility-device-util \
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/externalservice/service/AndroidManifest.xml b/tests/tests/externalservice/service/AndroidManifest.xml
index 06fa80e..74d0825 100644
--- a/tests/tests/externalservice/service/AndroidManifest.xml
+++ b/tests/tests/externalservice/service/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.externalservice.service">
<application android:label="External Service Host">
+ <uses-library android:name="android.test.runner" />
+
<!-- Service used to start .ExternalService from this package. -->
<service android:name=".ServiceCreator"
android:isolatedProcess="false"
diff --git a/tests/tests/graphics/Android.mk b/tests/tests/graphics/Android.mk
index 7ea5eec..2d05556 100644
--- a/tests/tests/graphics/Android.mk
+++ b/tests/tests/graphics/Android.mk
@@ -20,7 +20,7 @@
LOCAL_MULTILIB := both
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES += \
android-support-test \
@@ -29,8 +29,7 @@
ctsdeviceutillegacy \
ctstestrunner \
android-support-annotations \
- junit \
- legacy-android-test
+ junit
LOCAL_JNI_SHARED_LIBRARIES := libctsgraphics_jni
diff --git a/tests/tests/graphics/AndroidTest.xml b/tests/tests/graphics/AndroidTest.xml
index baa1e70..1cf31d9 100644
--- a/tests/tests/graphics/AndroidTest.xml
+++ b/tests/tests/graphics/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Graphics test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/graphics/assets/a3em.ttx b/tests/tests/graphics/assets/a3em.ttx
new file mode 100644
index 0000000..d3b9e16
--- /dev/null
+++ b/tests/tests/graphics/assets/a3em.ttx
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ <GlyphID id="2" name="3em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="1em" width="1000" lsb="93"/>
+ <mtx name="3em" width="3000" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="3em" />
+ <map code="0x0062" name="1em" />
+ <map code="0x0063" name="1em" />
+ <map code="0x0064" name="1em" />
+ <map code="0x0065" name="1em" />
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/b3em.ttx b/tests/tests/graphics/assets/b3em.ttx
new file mode 100644
index 0000000..b5a77ef
--- /dev/null
+++ b/tests/tests/graphics/assets/b3em.ttx
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ <GlyphID id="2" name="3em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="1em" width="1000" lsb="93"/>
+ <mtx name="3em" width="3000" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="1em" />
+ <map code="0x0062" name="3em" />
+ <map code="0x0063" name="1em" />
+ <map code="0x0064" name="1em" />
+ <map code="0x0065" name="1em" />
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/c3em.ttx b/tests/tests/graphics/assets/c3em.ttx
new file mode 100644
index 0000000..f5ed8e5
--- /dev/null
+++ b/tests/tests/graphics/assets/c3em.ttx
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ <GlyphID id="2" name="3em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="1.0"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="1em" width="1000" lsb="93"/>
+ <mtx name="3em" width="3000" lsb="93"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="1em" />
+ <map code="0x0062" name="1em" />
+ <map code="0x0063" name="3em" />
+ <map code="0x0064" name="1em" />
+ <map code="0x0065" name="1em" />
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png
index 38339b7..32a7516 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_drawable_scale_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png
index a22f678..9ba7c00 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_arcto_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
index be487d1..6cafa84 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png
index 943fce5..b0b6eb5 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_create_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png
index b46363e..9f60a31 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_delete_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
index 01c445c..3d14630 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_evenodd_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
index 739eea1..87ffcc1 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_filltype_nonzero_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
index 4617f62..859df7e 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
index 4d6b84d..78ef42b 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
index c6540e8..29aee85 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
index 491e73e..1c13c30 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
index e335a92..f2cb106 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
index 57f2ae3..34e0571 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png
index e74c181..c9702c9 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_group_clip_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png
index 7450751..b0af7d4 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_heart_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png
index d010d79..e779718 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png
index 5ada060..7ba7191 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_random_path_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
index 5af7090..37a0d21 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
index 24b9662..b011284 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
index c9677a6..9e92c8a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
index 8882a7a..0262e57 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_repeated_st_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
index 143ce3e..1520397 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
index 9a5efd2..727bb48 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
index 2edc3c7..d68ab90 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_scale_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png
index 9822bc2..2830840 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_schedule_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png
index d12b142..b5d25be 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_settings_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png
index 3936c89..923347d 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png
index c5d06f6..74c30fa 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_2_pressed_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png
index 3936c89..923347d 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png
index c5d06f6..74c30fa 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_state_list_pressed_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
index 2bf7882..c202ffc 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png
index 4141d6f..ed31e47 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png
index 1212fb3..a32de90 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_stroke_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
index 0717399..26cbe2a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
index 505aa2e..9aeeeef 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
index 9b53e94..468a1c3 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png
index a5d4d33..e4bc055 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_4_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png
index 0d8ded1..bb185f2 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_5_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png
index 3da7969..02901b6 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_transformation_6_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/font/a3em.ttf b/tests/tests/graphics/res/font/a3em.ttf
new file mode 100644
index 0000000..a601ce2
--- /dev/null
+++ b/tests/tests/graphics/res/font/a3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/b3em.ttf b/tests/tests/graphics/res/font/b3em.ttf
new file mode 100644
index 0000000..63948a2
--- /dev/null
+++ b/tests/tests/graphics/res/font/b3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/c3em.ttf b/tests/tests/graphics/res/font/c3em.ttf
new file mode 100644
index 0000000..badc3e2
--- /dev/null
+++ b/tests/tests/graphics/res/font/c3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/res/font/multistyle_family.xml b/tests/tests/graphics/res/font/multistyle_family.xml
new file mode 100644
index 0000000..2522e72
--- /dev/null
+++ b/tests/tests/graphics/res/font/multistyle_family.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:font="@font/a3em" android:fontWeight="400" android:fontStyle="normal" />
+ <font android:font="@font/b3em" android:fontWeight="400" android:fontStyle="italic" />
+ <font android:font="@font/c3em" android:fontWeight="700" android:fontStyle="italic" />
+</font-family>
diff --git a/tests/tests/graphics/res/font/multiweight_family.xml b/tests/tests/graphics/res/font/multiweight_family.xml
new file mode 100644
index 0000000..2ed0490
--- /dev/null
+++ b/tests/tests/graphics/res/font/multiweight_family.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:font="@font/a3em" android:fontWeight="100" android:fontStyle="normal" />
+ <font android:font="@font/b3em" android:fontWeight="400" android:fontStyle="normal" />
+ <font android:font="@font/c3em" android:fontWeight="700" android:fontStyle="normal" />
+</font-family>
diff --git a/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java b/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java
index b315bc9..0fb5978 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BlurMaskFilterTest.java
@@ -35,6 +35,7 @@
public class BlurMaskFilterTest {
private static final int OFFSET = 10;
private static final int RADIUS = 5;
+ private static final int CHECK_RADIUS = 8;
private static final int BITMAP_WIDTH = 100;
private static final int BITMAP_HEIGHT = 100;
private static final int CENTER = BITMAP_HEIGHT / 2;
@@ -51,13 +52,13 @@
canvas.drawRect(CENTER - OFFSET, CENTER - OFFSET, CENTER + OFFSET, CENTER + OFFSET, paint);
for (int x = 0; x < CENTER; x++) {
for (int y = 0; y < CENTER; y++) {
- if (x < CENTER - OFFSET - RADIUS || y < CENTER - OFFSET - RADIUS) {
+ if (x < CENTER - OFFSET - CHECK_RADIUS || y < CENTER - OFFSET - CHECK_RADIUS) {
// check that color didn't bleed (much) beyond radius
verifyQuadrants(Color.TRANSPARENT, b, x, y, 5);
} else if (x > CENTER - OFFSET + RADIUS && y > CENTER - OFFSET + RADIUS) {
// check that color didn't wash out (much) in the center
- verifyQuadrants(Color.RED, b, x, y, 5);
- } else {
+ verifyQuadrants(Color.RED, b, x, y, 8);
+ } else if (x > CENTER - OFFSET - RADIUS && y > CENTER - OFFSET - RADIUS) {
// check blur zone, color should remain, alpha varies
verifyQuadrants(Color.RED, b, x, y, 255);
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
index b36c23a..70c3202 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
@@ -1428,6 +1428,12 @@
// normal case
mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, 11, false, mPaint);
mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, 11, true, mPaint);
+
+ // special case: sweepAngle >= abs(360)
+ mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, 400, true, mPaint);
+ mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, -400, true, mPaint);
+ mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, Float.POSITIVE_INFINITY, true, mPaint);
+ mCanvas.drawArc(new RectF(0, 0, 10, 12), 10, Float.NEGATIVE_INFINITY, true, mPaint);
}
@Test(expected=NullPointerException.class)
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
index 04d5564..32cdd38 100644
--- a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
@@ -52,6 +52,19 @@
private static final String DEFAULT = (String)null;
private static final String INVALID = "invalid-family-name";
+ private static final float GLYPH_1EM_WIDTH;
+ private static final float GLYPH_3EM_WIDTH;
+
+ static {
+ // 3em.ttf supports "a", "b", "c". The width of "a" is 3em, others are 1em.
+ final Context ctx = InstrumentationRegistry.getTargetContext();
+ final Typeface typeface = ctx.getResources().getFont(R.font.a3em);
+ final Paint paint = new Paint();
+ paint.setTypeface(typeface);
+ GLYPH_3EM_WIDTH = paint.measureText("a");
+ GLYPH_1EM_WIDTH = paint.measureText("b");
+ }
+
// list of family names to try when attempting to find a typeface with a given style
private static final String[] FAMILIES =
{ (String) null, "monospace", "serif", "sans-serif", "cursive", "arial", "times" };
@@ -531,4 +544,68 @@
assertTrue(Math.abs(widthFromRegular - widthFromBlack) > 1.0f);
}
+
+ @Test
+ public void testTypefaceCreate_withExactWeight() {
+ // multiweight_family has following fonts.
+ // - a3em.ttf with weight = 100, style=normal configuration.
+ // This font supports "a", "b", "c". The weight "a" is 3em, others are 1em.
+ // - b3em.ttf with weight = 400, style=normal configuration.
+ // This font supports "a", "b", "c". The weight "b" is 3em, others are 1em.
+ // - c3em.ttf with weight = 700, style=normal configuration.
+ // This font supports "a", "b", "c". The weight "c" is 3em, others are 1em.
+ final Typeface family = mContext.getResources().getFont(R.font.multiweight_family);
+ assertNotNull(family);
+
+ final Paint paint = new Paint();
+ // By default, the font which weight is 400 is selected.
+ paint.setTypeface(family);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+ // Draw with the font which weight is 100.
+ paint.setTypeface(Typeface.create(family, 100 /* weight */, false /* italic */));
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+ // Draw with the font which weight is 700.
+ paint.setTypeface(Typeface.create(family, 700 /* weight */, false /* italic */));
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0f);
+ }
+
+ @Test
+ public void testTypefaceCreate_withExactStyle() {
+ // multiweight_family has following fonts.
+ // - a3em.ttf with weight = 400, style=normal configuration.
+ // This font supports "a", "b", "c". The weight "a" is 3em, others are 1em.
+ // - b3em.ttf with weight = 400, style=italic configuration.
+ // This font supports "a", "b", "c". The weight "b" is 3em, others are 1em.
+ // - c3em.ttf with weight = 700, style=italic configuration.
+ // This font supports "a", "b", "c". The weight "c" is 3em, others are 1em.
+ final Typeface family = mContext.getResources().getFont(R.font.multistyle_family);
+ assertNotNull(family);
+
+ final Paint paint = new Paint();
+ // By default, the normal style font which weight is 400 is selected.
+ paint.setTypeface(family);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+ // Draw with the italic font.
+ paint.setTypeface(Typeface.create(family, 400 /* weight */, true /* italic */));
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+ // Draw with the italic font which weigth is 700.
+ paint.setTypeface(Typeface.create(family, 700 /* weight */, true /* italic */));
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0f);
+ }
}
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 3adf811..62c67fc 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -26,7 +26,7 @@
LOCAL_MULTILIB := both
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
@@ -34,8 +34,7 @@
ctstestrunner \
mockito-target-minus-junit4 \
platform-test-annotations \
- ub-uiautomator \
- legacy-android-test
+ ub-uiautomator
LOCAL_JNI_SHARED_LIBRARIES := libctshardware_jni libnativehelper_compat_libc++
diff --git a/tests/tests/hardware/AndroidTest.xml b/tests/tests/hardware/AndroidTest.xml
index 6dbda37..f1fa92d 100644
--- a/tests/tests/hardware/AndroidTest.xml
+++ b/tests/tests/hardware/AndroidTest.xml
@@ -13,6 +13,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Hardware test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="misc" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java b/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java
index 75d7b7c..8968442 100644
--- a/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/GeomagneticFieldTest.java
@@ -23,26 +23,79 @@
import java.util.GregorianCalendar;
public class GeomagneticFieldTest extends AndroidTestCase {
- // Chengdu: Latitude 30d 40' 12", Longitude 104d 3' 36"
- private static final float LATITUDE_OF_CHENGDU = 30.67f;
- private static final float LONGITUDE_OF_CHENGDU = 104.06f;
- private static final float ALTITUDE_OF_CHENGDU = 500f;
- private static final long TEST_TIME = new GregorianCalendar(2010, 5, 1).getTimeInMillis();
+ private static final float DECLINATION_THRESHOLD = 0.1f;
+ private static final float INCLINATION_THRESHOLD = 0.1f;
+ private static final float FIELD_STRENGTH_THRESHOLD = 100;
@Presubmit
public void testGeomagneticField() {
- GeomagneticField geomagneticField = new GeomagneticField(LATITUDE_OF_CHENGDU,
- LONGITUDE_OF_CHENGDU, ALTITUDE_OF_CHENGDU, TEST_TIME);
+ // Reference values calculated from NOAA online calculator for WMM 2015
+ // https://www.ngdc.noaa.gov/geomag-web/#igrfwmm
+ TestDataPoint testPoints[] = new TestDataPoint[] {
+ // Mountain View, CA, USA on 2017/1/1
+ new TestDataPoint(37.386f, -122.083f, 32, 2017, 1, 1,
+ 13.4589f, 60.9542f, 48168.0f),
+ // Chengdu, China on 2017/8/8
+ new TestDataPoint(30.658f, 103.935f, 500f, 2017, 8, 8,
+ -1.9784f, 47.9723f, 50717.3f),
+ // Sao Paulo, Brazil on 2018/12/25
+ new TestDataPoint(-23.682f, -46.875f, 760f, 2018, 12, 25,
+ -21.3130f, -37.9940f, 22832.3f),
+ // Boston, MA, USA on 2019/2/10
+ new TestDataPoint(42.313f, -71.127f, 43f, 2019, 2, 10,
+ -14.5391f, 66.9693f, 51815.1f),
+ // Cape Town, South Africa on 2019/5/1
+ new TestDataPoint(-33.913f, 18.095f, 100f, 2019, 5, 1,
+ -25.2454f, -65.8887f, 25369.2f),
+ // Sydney, Australia on 2020/1/1
+ new TestDataPoint(-33.847f, 150.791f, 19f, 2020, 1, 1,
+ 12.4469f, -64.3443f, 57087.9f)
+ };
- // Reference values calculated from NOAA online calculator for WMM 2010,
- // and cross-checked in Matlab. The expected deltas are proportional to the
- // magnitude of each value.
- assertEquals(-1.867f, geomagneticField.getDeclination(), 0.1f);
- assertEquals(47.133f, geomagneticField.getInclination(), 1.0f);
- assertEquals(50375.6f, geomagneticField.getFieldStrength(), 100.0f);
- assertEquals(34269.3f, geomagneticField.getHorizontalStrength(), 100.0f);
- assertEquals(34251.2f, geomagneticField.getX(), 100.0f);
- assertEquals(-1113.2f, geomagneticField.getY(), 10.0f);
- assertEquals(36923.1f, geomagneticField.getZ(), 100.0f);
+ for (TestDataPoint t : testPoints) {
+ GeomagneticField field =
+ new GeomagneticField(t.latitude, t.longitude, t.altitude, t.epochTimeMillis);
+ assertEquals(t.declinationDegree, field.getDeclination(), DECLINATION_THRESHOLD);
+ assertEquals(t.inclinationDegree, field.getInclination(), INCLINATION_THRESHOLD);
+ assertEquals(t.fieldStrengthNanoTesla, field.getFieldStrength(),
+ FIELD_STRENGTH_THRESHOLD);
+
+ float horizontalFieldStrengthNanoTesla = (float)(
+ Math.cos(Math.toRadians(t.inclinationDegree)) * t.fieldStrengthNanoTesla);
+ assertEquals(horizontalFieldStrengthNanoTesla, field.getHorizontalStrength(),
+ FIELD_STRENGTH_THRESHOLD);
+
+ float verticalFieldStrengthNanoTesla = (float)(
+ Math.sin(Math.toRadians(t.inclinationDegree)) * t.fieldStrengthNanoTesla);
+ assertEquals(verticalFieldStrengthNanoTesla, field.getZ(), FIELD_STRENGTH_THRESHOLD);
+
+ float declinationDegree = (float)(
+ Math.toDegrees(Math.atan2(field.getY(), field.getX())));
+ assertEquals(t.declinationDegree, declinationDegree, DECLINATION_THRESHOLD);
+ assertEquals(horizontalFieldStrengthNanoTesla,
+ Math.sqrt(field.getX() * field.getX() + field.getY() * field.getY()),
+ FIELD_STRENGTH_THRESHOLD);
+ }
+ }
+
+ private class TestDataPoint {
+ public final float latitude;
+ public final float longitude;
+ public final float altitude;
+ public final long epochTimeMillis;
+ public final float declinationDegree;
+ public final float inclinationDegree;
+ public final float fieldStrengthNanoTesla;
+
+ TestDataPoint(float lat, float lng, float alt, int year, int month, int day,
+ float dec, float inc, float strength) {
+ latitude = lat;
+ longitude = lng;
+ altitude = alt;
+ epochTimeMillis = new GregorianCalendar(year, month, day).getTimeInMillis();
+ declinationDegree = dec;
+ inclinationDegree = inc;
+ fieldStrengthNanoTesla = strength;
+ }
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/GlUtils.java b/tests/tests/hardware/src/android/hardware/cts/GlUtils.java
index 81628d4..bd5cecb 100644
--- a/tests/tests/hardware/src/android/hardware/cts/GlUtils.java
+++ b/tests/tests/hardware/src/android/hardware/cts/GlUtils.java
@@ -17,6 +17,7 @@
package android.hardware.cts;
import android.opengl.GLES20;
+import android.util.Pair;
import java.util.Arrays;
import java.util.regex.Matcher;
@@ -27,6 +28,10 @@
}
static int getMajorVersion() {
+ return getVersion().first;
+ }
+
+ static Pair<Integer, Integer> getVersion() {
// Section 6.1.5 of the OpenGL ES specification indicates the GL version
// string strictly follows this format:
//
@@ -42,9 +47,10 @@
Pattern pattern = Pattern.compile("OpenGL ES ([0-9]+)\\.([0-9]+)");
Matcher matcher = pattern.matcher(version);
if (matcher.find()) {
- return Integer.parseInt(matcher.group(1));
+ return new Pair<>(
+ Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
}
- return 2;
+ return new Pair<>(2, 0);
}
static String[] getExtensions() {
diff --git a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
index 5ea845a..6276959 100644
--- a/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/HardwareBufferTest.java
@@ -24,6 +24,7 @@
import android.opengl.EGLSurface;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.Pair;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,8 +71,10 @@
}, 0);
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
- sHasFloatBuffers = GlUtils.getMajorVersion() >= 3 ||
- GlUtils.hasExtensions("GL_OES_texture_half_float",
+ Pair<Integer, Integer> version = GlUtils.getVersion();
+ sHasFloatBuffers = (version.first >= 3 && version.second >= 2) ||
+ GlUtils.hasExtensions(
+ "GL_OES_texture_half_float",
"GL_OES_texture_half_float_linear");
EGL14.eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
diff --git a/tests/tests/incident/Android.mk b/tests/tests/incident/Android.mk
index 1309c99..6c6ef62 100644
--- a/tests/tests/incident/Android.mk
+++ b/tests/tests/incident/Android.mk
@@ -33,7 +33,6 @@
LOCAL_JAVA_LIBRARIES += android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/jni/AndroidTest.xml b/tests/tests/jni/AndroidTest.xml
index 84cf235..e4ab2dd 100644
--- a/tests/tests/jni/AndroidTest.xml
+++ b/tests/tests/jni/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS JNI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/jni_vendor/AndroidTest.xml b/tests/tests/jni_vendor/AndroidTest.xml
index 65afd6b..b606559 100644
--- a/tests/tests/jni_vendor/AndroidTest.xml
+++ b/tests/tests/jni_vendor/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Vendor's JNI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/jvmti/attaching/AndroidTest.xml b/tests/tests/jvmti/attaching/AndroidTest.xml
index 1b4ed05..5723956 100644
--- a/tests/tests/jvmti/attaching/AndroidTest.xml
+++ b/tests/tests/jvmti/attaching/AndroidTest.xml
@@ -17,6 +17,7 @@
-->
<configuration description="Config for CTS jvmti attaching test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/keystore/Android.mk b/tests/tests/keystore/Android.mk
index ea24954..c88f614 100644
--- a/tests/tests/keystore/Android.mk
+++ b/tests/tests/keystore/Android.mk
@@ -32,7 +32,7 @@
ctstestrunner \
guava \
junit \
- legacy-android-test
+ cts-security-test-support-library
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -49,6 +49,7 @@
# Uncomment when b/13282254 is fixed.
# LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.base
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/keystore/AndroidTest.xml b/tests/tests/keystore/AndroidTest.xml
index 87913ed..e2b4986 100644
--- a/tests/tests/keystore/AndroidTest.xml
+++ b/tests/tests/keystore/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Keystore test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestationPackageInfo.java b/tests/tests/keystore/src/android/keystore/cts/AttestationPackageInfo.java
deleted file mode 100644
index 294fda8..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/AttestationPackageInfo.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.keystore.cts;
-
-import com.android.org.bouncycastle.asn1.ASN1Encodable;
-import com.android.org.bouncycastle.asn1.ASN1Sequence;
-
-import java.security.cert.CertificateParsingException;
-
-import java.io.UnsupportedEncodingException;
-
-public class AttestationPackageInfo implements java.lang.Comparable<AttestationPackageInfo> {
- private static final int PACKAGE_NAME_INDEX = 0;
- private static final int VERSION_INDEX = 1;
-
- private final String packageName;
- private final int version;
-
- public AttestationPackageInfo(String packageName, int version) {
- this.packageName = packageName;
- this.version = version;
- }
-
- public AttestationPackageInfo(ASN1Encodable asn1Encodable) throws CertificateParsingException {
- if (!(asn1Encodable instanceof ASN1Sequence)) {
- throw new CertificateParsingException(
- "Expected sequence for AttestationPackageInfo, found "
- + asn1Encodable.getClass().getName());
- }
-
- ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
- try {
- packageName = Asn1Utils.getStringFromAsn1OctetStreamAssumingUTF8(
- sequence.getObjectAt(PACKAGE_NAME_INDEX));
- } catch (UnsupportedEncodingException e) {
- throw new CertificateParsingException(
- "Converting octet stream to String triggered an UnsupportedEncodingException",
- e);
- }
- version = Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERSION_INDEX));
- }
-
- public String getPackageName() {
- return packageName;
- }
-
- public int getVersion() {
- return version;
- }
-
- @Override
- public String toString() {
- return new StringBuilder().append("Package name: ").append(getPackageName())
- .append("\nVersion: " + getVersion()).toString();
- }
-
- @Override
- public int compareTo(AttestationPackageInfo other) {
- int res = packageName.compareTo(other.packageName);
- if (res != 0) return res;
- res = Integer.compare(version, other.version);
- if (res != 0) return res;
- return res;
- }
-
- @Override
- public boolean equals(Object o) {
- return (o instanceof AttestationPackageInfo)
- && (0 == compareTo((AttestationPackageInfo) o));
- }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index d901690..0342f08 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -62,6 +62,9 @@
import android.test.AndroidTestCase;
import android.util.ArraySet;
+import com.android.org.bouncycastle.asn1.x500.X500Name;
+import com.android.org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -70,6 +73,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.ProviderException;
+import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
@@ -422,7 +426,7 @@
try {
Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
- verifyCertificateSignatures(certificates);
+ verifyCertificateChain(certificates);
X509Certificate attestationCert = (X509Certificate) certificates[0];
Attestation attestation = new Attestation(attestationCert);
@@ -476,7 +480,7 @@
try {
Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
- verifyCertificateSignatures(certificates);
+ verifyCertificateChain(certificates);
X509Certificate attestationCert = (X509Certificate) certificates[0];
Attestation attestation = new Attestation(attestationCert);
@@ -876,12 +880,36 @@
keyPairGenerator.generateKeyPair();
}
- private void verifyCertificateSignatures(Certificate[] certChain)
+ private void verifyCertificateChain(Certificate[] certChain)
throws GeneralSecurityException {
assertNotNull(certChain);
for (int i = 1; i < certChain.length; ++i) {
try {
- certChain[i - 1].verify(certChain[i].getPublicKey());
+ PublicKey pubKey = certChain[i].getPublicKey();
+ certChain[i - 1].verify(pubKey);
+ if (i == certChain.length - 1) {
+ // Last cert should be self-signed.
+ certChain[i].verify(pubKey);
+ }
+
+ // Check that issuer in the signed cert matches subject in the signing cert.
+ X509Certificate x509CurrCert = (X509Certificate) certChain[i];
+ X509Certificate x509PrevCert = (X509Certificate) certChain[i - 1];
+ X500Name signingCertSubject =
+ new JcaX509CertificateHolder(x509CurrCert).getSubject();
+ X500Name signedCertIssuer =
+ new JcaX509CertificateHolder(x509PrevCert).getIssuer();
+ // Use .toASN1Object().equals() rather than .equals() because .equals() is case
+ // insensitive, and we want to verify an exact match.
+ assertTrue(
+ signedCertIssuer.toASN1Object().equals(signingCertSubject.toASN1Object()));
+
+ if (i == 1) {
+ // First cert should have subject "CN=Android Keystore Key".
+ X500Name signedCertSubject =
+ new JcaX509CertificateHolder(x509PrevCert).getSubject();
+ assertEquals(signedCertSubject, new X500Name("CN=Android Keystore Key"));
+ }
} catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
| NoSuchProviderException | SignatureException e) {
throw new GeneralSecurityException("Failed to verify certificate "
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
index d64e7b7..229b69c 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
@@ -41,6 +41,8 @@
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1682,8 +1684,15 @@
assertEquals(expected.getKey(alias, null),
actual.getKey(alias, null));
} else {
- assertEquals(expected.getKey(alias, PASSWORD_KEY),
- actual.getKey(alias, PASSWORD_KEY));
+ Key actualKey = actual.getKey(alias, PASSWORD_KEY);
+ Key expectedKey = expected.getKey(alias, PASSWORD_KEY);
+ if (actualKey instanceof RSAPrivateKey &&
+ expectedKey instanceof RSAPrivateKey) {
+ assertEquals(((RSAPrivateKey)actualKey).getModulus(),
+ ((RSAPrivateKey)expectedKey).getModulus());
+ } else {
+ assertEquals(actualKey, expectedKey);
+ }
}
assertEquals(expected.getCertificate(alias), actual.getCertificate(alias));
}
diff --git a/tests/tests/libcorefileio/Android.mk b/tests/tests/libcorefileio/Android.mk
index cfd9775..bc224f6 100644
--- a/tests/tests/libcorefileio/Android.mk
+++ b/tests/tests/libcorefileio/Android.mk
@@ -21,7 +21,9 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/libcorefileio/AndroidTest.xml b/tests/tests/libcorefileio/AndroidTest.xml
index c2b230e..074a833 100644
--- a/tests/tests/libcorefileio/AndroidTest.xml
+++ b/tests/tests/libcorefileio/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Legacy Libcore test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="libcore" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/location/Android.mk b/tests/tests/location/Android.mk
index a0b8142..6e90050 100644
--- a/tests/tests/location/Android.mk
+++ b/tests/tests/location/Android.mk
@@ -26,6 +26,8 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_JAVA_LIBRARIES := telephony-common android.test.base.stubs
+
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util ctstestrunner apache-commons-math
@@ -50,6 +52,8 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_JAVA_LIBRARIES := telephony-common android.test.base.stubs
+
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner apache-commons-math
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
diff --git a/tests/tests/location/AndroidManifest.xml b/tests/tests/location/AndroidManifest.xml
index 1357a38..6e3bbf1 100644
--- a/tests/tests/location/AndroidManifest.xml
+++ b/tests/tests/location/AndroidManifest.xml
@@ -28,9 +28,15 @@
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.ACCESS_WIFI_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.INTERNET" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.READ_SMS"/>
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.SEND_SMS" />
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="android.location.cts"
android:label="CTS tests of android.location">
diff --git a/tests/tests/location/AndroidTest.xml b/tests/tests/location/AndroidTest.xml
index 4f55a93..c69e224 100644
--- a/tests/tests/location/AndroidTest.xml
+++ b/tests/tests/location/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Location test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="location" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
new file mode 100644
index 0000000..d7dbc89
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
@@ -0,0 +1,333 @@
+package android.location.cts;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+import com.google.android.mms.pdu.CharacterSets;
+import com.google.android.mms.pdu.EncodedStringValue;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.PduBody;
+import com.google.android.mms.pdu.PduComposer;
+import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.PduPart;
+import com.google.android.mms.pdu.SendConf;
+import com.google.android.mms.pdu.SendReq;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test sending SMS and MMS using {@link android.telephony.SmsManager}.
+ */
+public class EmergencyCallMessageTest extends GnssTestCase {
+
+ private static final String TAG = "EmergencyCallMSGTest";
+
+ private static final String ACTION_MMS_SENT = "CTS_MMS_SENT_ACTION";
+ private static final long DEFAULT_EXPIRY_TIME_SECS = TimeUnit.DAYS.toSeconds(7);
+ private static final long MMS_CONFIG_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1);
+ private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
+ private static final short DEFAULT_DATA_SMS_PORT = 8091;
+ private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber";
+ private static final String SUBJECT = "CTS Emergency Call MMS Test";
+ private static final String MMS_MESSAGE_BODY = "CTS Emergency Call MMS test message body";
+ private static final String SMS_MESSAGE_BODY = "CTS Emergency Call Sms test message body";
+ private static final String SMS_DATA_MESSAGE_BODY =
+ "CTS Emergency Call Sms data test message body";
+ private static final String TEXT_PART_FILENAME = "text_0.txt";
+ private static final String SMIL_TEXT =
+ "<smil>" +
+ "<head>" +
+ "<layout>" +
+ "<root-layout/>" +
+ "<region height=\"100%%\" id=\"Text\" left=\"0%%\" top=\"0%%\" width=\"100%%\"/>" +
+ "</layout>" +
+ "</head>" +
+ "<body>" +
+ "<par dur=\"8000ms\">" +
+ "<text src=\"%s\" region=\"Text\"/>" +
+ "</par>" +
+ "</body>" +
+ "</smil>";
+
+ private static final long SENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); // 5 minutes
+
+ private static final String PROVIDER_AUTHORITY = "emergencycallverifier";
+
+ private Random mRandom;
+ private SentReceiver mSentReceiver;
+ private TelephonyManager mTelephonyManager;
+ private PackageManager mPackageManager;
+
+ private static class SentReceiver extends BroadcastReceiver {
+ private boolean mSuccess;
+ private boolean mDone;
+ private final CountDownLatch mLatch;
+ public SentReceiver() {
+ mLatch = new CountDownLatch(1);
+ mSuccess = false;
+ mDone = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Action " + intent.getAction());
+ if (!ACTION_MMS_SENT.equals(intent.getAction())) {
+ return;
+ }
+ final int resultCode = getResultCode();
+ if (resultCode == Activity.RESULT_OK) {
+ final byte[] response = intent.getByteArrayExtra(SmsManager.EXTRA_MMS_DATA);
+ if (response != null) {
+ final GenericPdu pdu = new PduParser(
+ response, shouldParseContentDisposition()).parse();
+ if (pdu != null && pdu instanceof SendConf) {
+ final SendConf sendConf = (SendConf) pdu;
+ if (sendConf.getResponseStatus() == PduHeaders.RESPONSE_STATUS_OK) {
+ mSuccess = true;
+ } else {
+ Log.e(TAG, "SendConf response status=" + sendConf.getResponseStatus());
+ }
+ } else {
+ Log.e(TAG, "Not a SendConf: " +
+ (pdu != null ? pdu.getClass().getCanonicalName() : "NULL"));
+ }
+ } else {
+ Log.e(TAG, "Empty response");
+ }
+ } else {
+ Log.e(TAG, "Failure result=" + resultCode);
+ if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) {
+ final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0);
+ Log.e(TAG, "HTTP failure=" + httpError);
+ }
+ }
+ mDone = true;
+ mLatch.countDown();
+ }
+
+ public boolean waitForSuccess(long timeoutMs) throws Exception {
+ mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess);
+ return mDone && mSuccess;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mRandom = new Random(System.currentTimeMillis());
+ mTelephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ public void testSendSmsMessage() {
+ // this test is only for cts verifier
+ if (!isCtsVerifierTest()) {
+ return;
+ }
+ SmsManager smsManager = SmsManager.getDefault();
+ final String selfNumber = getPhoneNumber(mContext);
+ smsManager.sendTextMessage(selfNumber, null, SMS_MESSAGE_BODY, null, null);
+ }
+
+ public void testSendSmsDataMessage() {
+ // this test is only for cts verifier
+ if (!isCtsVerifierTest()) {
+ return;
+ }
+ SmsManager smsManager = SmsManager.getDefault();
+ final String selfNumber = getPhoneNumber(mContext);
+ smsManager.sendDataMessage(selfNumber, null, DEFAULT_DATA_SMS_PORT,
+ SMS_DATA_MESSAGE_BODY.getBytes(), null, null);
+ }
+
+ public void testSendMmsMessage() throws Exception {
+ // this test is only for cts verifier
+ if (!isCtsVerifierTest()) {
+ return;
+ }
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ || !doesSupportMMS()) {
+ Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
+ return;
+ }
+
+ Log.i(TAG, "testSendMmsMessage");
+ // Prime the MmsService so that MMS config is loaded
+ final SmsManager smsManager = SmsManager.getDefault();
+ // MMS config is loaded asynchronously. Wait a bit so it will be loaded.
+ try {
+ Thread.sleep(MMS_CONFIG_DELAY_MILLIS);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+
+ final Context context = getContext();
+ // Register sent receiver
+ mSentReceiver = new SentReceiver();
+ context.registerReceiver(mSentReceiver, new IntentFilter(ACTION_MMS_SENT));
+ // Create local provider file for sending PDU
+ final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
+ final File sendFile = new File(context.getCacheDir(), fileName);
+ final String selfNumber = getPhoneNumber(context);
+ assertTrue(!TextUtils.isEmpty(selfNumber));
+ final byte[] pdu = buildPdu(context, selfNumber, SUBJECT, MMS_MESSAGE_BODY);
+ assertNotNull(pdu);
+ assertTrue(writePdu(sendFile, pdu));
+ final Uri contentUri = (new Uri.Builder())
+ .authority(PROVIDER_AUTHORITY)
+ .path(fileName)
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .build();
+ // Send
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context, 0, new Intent(ACTION_MMS_SENT), 0);
+ smsManager.sendMultimediaMessage(context,
+ contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
+ assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT_MILLIS));
+ sendFile.delete();
+ }
+
+ private static boolean writePdu(File file, byte[] pdu) {
+ FileOutputStream writer = null;
+ try {
+ writer = new FileOutputStream(file);
+ writer.write(pdu);
+ return true;
+ } catch (final IOException e) {
+ String stackTrace = Log.getStackTraceString(e);
+ Log.i(TAG, stackTrace);
+ return false;
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private static byte[] buildPdu(Context context, String selfNumber, String subject,
+ String text) {
+ final SendReq req = new SendReq();
+ // From, per spec
+ req.setFrom(new EncodedStringValue(selfNumber));
+ // To
+ final String[] recipients = new String[1];
+ recipients[0] = selfNumber;
+ final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients);
+ if (encodedNumbers != null) {
+ req.setTo(encodedNumbers);
+ }
+ // Subject
+ if (!TextUtils.isEmpty(subject)) {
+ req.setSubject(new EncodedStringValue(subject));
+ }
+ // Date
+ req.setDate(System.currentTimeMillis() / 1000);
+ // Body
+ final PduBody body = new PduBody();
+ // Add text part. Always add a smil part for compatibility, without it there
+ // may be issues on some carriers/client apps
+ final int size = addTextPart(body, text, true/* add text smil */);
+ req.setBody(body);
+ // Message size
+ req.setMessageSize(size);
+ // Message class
+ req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
+ // Expiry
+ req.setExpiry(DEFAULT_EXPIRY_TIME_SECS);
+ // The following set methods throw InvalidHeaderValueException
+ try {
+ // Priority
+ req.setPriority(DEFAULT_PRIORITY);
+ // Delivery report
+ req.setDeliveryReport(PduHeaders.VALUE_NO);
+ // Read report
+ req.setReadReport(PduHeaders.VALUE_NO);
+ } catch (InvalidHeaderValueException e) {
+ return null;
+ }
+
+ return new PduComposer(context, req).make();
+ }
+
+ private static int addTextPart(PduBody pb, String message, boolean addTextSmil) {
+ final PduPart part = new PduPart();
+ // Set Charset if it's a text media.
+ part.setCharset(CharacterSets.UTF_8);
+ // Set Content-Type.
+ part.setContentType(ContentType.TEXT_PLAIN.getBytes());
+ // Set Content-Location.
+ part.setContentLocation(TEXT_PART_FILENAME.getBytes());
+ int index = TEXT_PART_FILENAME.lastIndexOf(".");
+ String contentId = (index == -1) ? TEXT_PART_FILENAME
+ : TEXT_PART_FILENAME.substring(0, index);
+ part.setContentId(contentId.getBytes());
+ part.setData(message.getBytes());
+ pb.addPart(part);
+ if (addTextSmil) {
+ final String smil = String.format(SMIL_TEXT, TEXT_PART_FILENAME);
+ addSmilPart(pb, smil);
+ }
+ return part.getData().length;
+ }
+
+ private static void addSmilPart(PduBody pb, String smil) {
+ final PduPart smilPart = new PduPart();
+ smilPart.setContentId("smil".getBytes());
+ smilPart.setContentLocation("smil.xml".getBytes());
+ smilPart.setContentType(ContentType.APP_SMIL.getBytes());
+ smilPart.setData(smil.getBytes());
+ pb.addPart(0, smilPart);
+ }
+
+ private static String getPhoneNumber(Context context) {
+ final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ String phoneNumber = telephonyManager.getLine1Number();
+ if (phoneNumber.trim().isEmpty()) {
+ phoneNumber = System.getProperty(PHONE_NUMBER_KEY);
+ }
+ return phoneNumber;
+ }
+
+ private static boolean shouldParseContentDisposition() {
+ return SmsManager
+ .getDefault()
+ .getCarrierConfigValues()
+ .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
+ }
+
+ private static boolean doesSupportMMS() {
+ return SmsManager
+ .getDefault()
+ .getCarrierConfigValues()
+ .getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED, true);
+ }
+
+}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
new file mode 100644
index 0000000..7ebb04a
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.location.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.telephony.TelephonyManager;
+import android.net.wifi.WifiManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class is used to test Wifi realted features.
+ */
+public class EmergencyCallWifiTest extends GnssTestCase {
+
+ private final static String TAG = EmergencyCallWifiTest.class.getCanonicalName();
+ private final static String LATCH_NAME = "EmergencyCallWifiTest";
+ private final static String GOOGLE_URL = "www.google.com";
+ private final static int WEB_PORT = 80;
+ private static final int BATCH_SCAN_BSSID_LIMIT = 25;
+ private static final int WIFI_SCAN_TIMEOUT_SEC = 30;
+ private static final long SETTINGS_PERIOD_MS = TimeUnit.SECONDS.toMillis(4);
+ private static final long INTERNET_CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+ private static final int MIN_SCAN_COUNT = 1;
+
+ private WifiManager mWifiManager;
+ private Context mContext;
+ private WifiScanReceiver mWifiScanReceiver;
+ private int mScanCounter = 0;
+ private CountDownLatch mLatch;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = getContext();
+ mWifiManager = (WifiManager) mContext.getSystemService(mContext.WIFI_SERVICE);
+ mWifiScanReceiver = new WifiScanReceiver();
+ }
+
+ public void testWifiScan() throws Exception {
+ mContext.registerReceiver(mWifiScanReceiver, new IntentFilter(
+ WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ mLatch = new CountDownLatch(1);
+ mWifiManager.startScan();
+ Log.d(TAG, "Waiting for wifiScan to complete.");
+ mLatch.await(WIFI_SCAN_TIMEOUT_SEC, TimeUnit.SECONDS);
+ if (mScanCounter < MIN_SCAN_COUNT) {
+ fail(String.format("Expected at least %d scans but only %d scans happened",
+ MIN_SCAN_COUNT, mScanCounter));
+ }
+ }
+
+ public void testWifiConnection() {
+ boolean isReachable =
+ isReachable(GOOGLE_URL, WEB_PORT, (int)INTERNET_CONNECTION_TIMEOUT);
+ assertTrue("Can not connect to google.com."
+ + " Please make sure device has the internet connection", isReachable);
+ }
+
+ private static boolean isReachable(String addr, int openPort, int timeOutMillis) {
+ try {
+ try (Socket soc = new Socket()) {
+ soc.connect(new InetSocketAddress(addr, openPort), timeOutMillis);
+ }
+ return true;
+ } catch (IOException ex) {
+ return false;
+ }
+ }
+
+ private class WifiScanReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context c, Intent intent) {
+ List <ScanResult> scanResults = mWifiManager.getScanResults();
+ Log.d(TAG, String.format("Got scan results with size %d", scanResults.size()));
+ for (ScanResult result : scanResults) {
+ Log.d(TAG, result.toString());
+ }
+ mScanCounter++;
+ mLatch.countDown();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java b/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
new file mode 100644
index 0000000..160e9c0
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.location.cts;
+
+import android.location.LocationManager;
+import android.util.Log;
+
+/**
+ * Test the {@link LocationManager#getGnssYearOfHardware} and
+ * {@link LocationManager#getGnssHardwareModelName} values.
+ */
+public class GnssHardwareInfoTest extends GnssTestCase {
+
+ private static final String TAG = "GnssHardwareInfoTest";
+ private static final int MIN_HARDWARE_YEAR = 2015;
+
+ /**
+ * Minimum plausible descriptive hardware model name length, e.g. "ABC1" for first GNSS version
+ * ever shipped by ABC company.
+ */
+ private static final int MIN_HARDWARE_MODEL_NAME_LENGTH = 4;
+ private static final int MIN_HARDWARE_YEAR_FOR_VALID_STRING = 2018;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ }
+
+ /**
+ * Verify hardware year is reported as 2015 or higher, with exception that in 2015, some
+ * devies did not implement the underlying HAL API, and thus will report 0.
+ */
+ public void testHardwareYear() throws Exception {
+ int gnssHardwareYear = mTestLocationManager.getLocationManager().getGnssYearOfHardware();
+ // Allow 0 until 2019, as older, upgrading devices may report 0.
+ assertTrue("Hardware year must be 2015 or higher",
+ gnssHardwareYear >= MIN_HARDWARE_YEAR || gnssHardwareYear == 0);
+ }
+
+ /**
+ * Verify GNSS hardware model year is reported as a valid, descriptive value.
+ * Descriptive is limited to a character count, and not the older values.
+ */
+ public void testHardwareModelName() throws Exception {
+ String gnssHardwareModelName =
+ mTestLocationManager.getLocationManager().getGnssHardwareModelName();
+ assertTrue("gnssHardwareModelName must not be null", gnssHardwareModelName != null);
+ assertTrue("gnssHardwareModelName must be descriptive - at least 4 characters long",
+ gnssHardwareModelName.length() >= MIN_HARDWARE_MODEL_NAME_LENGTH);
+
+ if (mTestLocationManager.getLocationManager().getGnssYearOfHardware() >=
+ MIN_HARDWARE_YEAR_FOR_VALID_STRING) {
+ assertFalse("gnssHardwareModelName must be descriptive - not default value",
+ gnssHardwareModelName.contentEquals(
+ LocationManager.GNSS_HARDWARE_MODEL_NAME_UNKNOWN));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java b/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
new file mode 100644
index 0000000..cd230d5
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * 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.location.cts;
+
+/**
+ * Test the "gps" location output works through various rate changes
+ *
+ * Tests:
+ * 1. Toggle through various rates.
+ * 2. Mix toggling through various rates with start & stop.
+ *
+ * Inspired by bugs 65246279, 65425110
+ */
+
+public class GnssLocationRateChangeTest extends GnssTestCase {
+
+ private static final String TAG = "GnssLocationRateChangeTest";
+ private static final int LOCATION_TO_COLLECT_COUNT = 1;
+
+ private TestLocationListener mLocationListenerMain;
+ private TestLocationListener mLocationListenerAfterRateChanges;
+ // Various rates, where underlying GNSS hardware states may enter different modes
+ private static final int[] TBF_MSEC = {0, 4_000, 250_000, 6_000_000, 10, 1_000, 16_000, 64_000};
+ private static final int LOOPS_FOR_STRESS_TEST = 20;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestLocationManager = new TestLocationManager(getContext());
+ // Using separate listeners, so the await trigger for the after-rate-changes listener is
+ // independent of any possible locations that flow during setup, and rate change stress
+ // testing
+ mLocationListenerMain = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mLocationListenerAfterRateChanges = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Unregister listeners
+ if (mLocationListenerMain != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+ }
+ if (mLocationListenerAfterRateChanges != null) {
+ mTestLocationManager.removeLocationUpdates(mLocationListenerAfterRateChanges);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Requests (GPS) Locations at various rates that may stress the underlying GNSS software
+ * and firmware state machine layers, ensuring Location output
+ * remains responsive after all is done.
+ */
+ public void testVariedRates() throws Exception {
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+ softAssert.assertTrue("Location should be received at test start",
+ mLocationListenerMain.await());
+
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ }
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+
+ mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+ softAssert.assertTrue("Location should be received at test end",
+ mLocationListenerAfterRateChanges.await());
+
+ softAssert.assertAll();
+ }
+
+ /**
+ * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+ * that may stress the underlying GNSS software
+ * and firmware state machine layers, ensuring Location output remains responsive
+ * after all is done.
+ */
+ public void testVariedRatesOnOff() throws Exception {
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+ softAssert.assertTrue("Location should be received at test start",
+ mLocationListenerMain.await());
+
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ // Also flip the requests on and off quickly
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+ }
+
+ mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+ softAssert.assertTrue("Location should be received at test end",
+ mLocationListenerAfterRateChanges.await());
+
+ softAssert.assertAll();
+ }
+
+ /**
+ * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+ * in multiple loops to provide additional stress to the underlying GNSS software
+ * and firmware state machine layers, ensuring Location output remains responsive
+ * after all is done.
+ */
+ public void testVariedRatesRepetitive() throws Exception {
+ SoftAssert softAssert = new SoftAssert(TAG);
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+ softAssert.assertTrue("Location should be received at test start",
+ mLocationListenerMain.await());
+
+ // Two loops, first without removes, then with removes
+ for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ }
+ }
+ for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+ for (int timeBetweenLocationsMsec : TBF_MSEC) {
+ mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+ timeBetweenLocationsMsec);
+ // Also flip the requests on and off quickly
+ mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+ }
+ }
+
+ mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+ softAssert.assertTrue("Location should be received at test end",
+ mLocationListenerAfterRateChanges.await());
+
+ softAssert.assertAll();
+ }
+}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java b/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
index 3411914..d2499a0 100644
--- a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
@@ -16,9 +16,7 @@
package android.location.cts;
-import android.location.Criteria;
import android.location.Location;
-import android.location.LocationManager;
import android.util.Log;
/**
@@ -133,14 +131,18 @@
success);
SoftAssert softAssert = new SoftAssert(TAG);
+ // don't check speed of first GNSS location - it may not be ready in some cases
+ boolean checkSpeed = false;
for (Location location : mLocationListener.getReceivedLocationList()) {
- checkLocationRegularFields(softAssert, location);
+ checkLocationRegularFields(softAssert, location, checkSpeed);
+ checkSpeed = true;
}
softAssert.assertAll();
}
- public static void checkLocationRegularFields(SoftAssert softAssert, Location location) {
+ public static void checkLocationRegularFields(SoftAssert softAssert, Location location,
+ boolean checkSpeed) {
// For the altitude: the unit is meter
// The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
// Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
@@ -177,13 +179,16 @@
softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
location.getLatitude() >= -90 && location.getLatitude() <= 90);
- softAssert.assertTrue("All GNSS locations generated by the LocationManager "
- + "must have speeds.", location.hasSpeed());
+ if (checkSpeed) {
+ softAssert.assertTrue("All but the first GNSS location from LocationManager "
+ + "must have speeds.", location.hasSpeed());
+ }
- // For the speed, during the cts test device shouldn't move faster than 1m/s
+ // For the speed, during the cts test device shouldn't move faster than 1m/s, but allowing up
+ // to 5m/s for possible early fix noise in moderate signal test environments
if(location.hasSpeed()) {
- softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 1] m/s",
- location.getSpeed() >= 0 && location.getSpeed() <= 1);
+ softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 5] m/s",
+ location.getSpeed() >= 0 && location.getSpeed() <= 5);
}
}
diff --git a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java b/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
index ec78ea3..d86d612 100644
--- a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
@@ -39,7 +39,7 @@
private static final int MEASUREMENT_EVENTS_TO_COLLECT_COUNT = 10;
private static final int MIN_SATELLITES_REQUIREMENT = 4;
private static final double SECONDS_PER_NANO = 1.0e-9;
- private static final double POSITION_THRESHOLD_IN_DEGREES = 0.003; // degrees (~= 300 meters)
+
// GPS/GLONASS: according to http://cdn.intechopen.com/pdfs-wm/27712.pdf, the pseudorange in time
// is 65-83 ms, which is 18 ms range.
// GLONASS: orbit is a bit closer than GPS, so we add 0.003ms to the range, hence deltaiSeconds
@@ -58,6 +58,12 @@
// that are the short end of the range.
private static final double PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC = 0.076;
+ private static final float LOW_ENOUGH_POSITION_UNCERTAINTY_METERS = 100;
+ private static final float LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS = 5;
+ private static final float HORIZONTAL_OFFSET_FLOOR_METERS = 10;
+ private static final float HORIZONTAL_OFFSET_SIGMA = 3; // 3 * the ~68%ile level
+ private static final float HORIZONTAL_OFFSET_FLOOR_MPS = 1;
+
private TestGnssMeasurementListener mMeasurementListener;
private TestLocationListener mLocationListener;
@@ -224,92 +230,173 @@
}
}
- /*
- * Use pseudorange calculation library to calculate position then compare to location from
- * Location Manager.
- */
- @CddTest(requirement="7.3.3")
- public void testPseudoPosition() throws Exception {
- // Checks if Gnss hardware feature is present, skips test (pass) if not,
- // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
- // From android O, CTS tests should run in the lab with GPS signal.
- if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
- TAG, MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED, true)) {
- return;
+ /*
+ * Use pseudorange calculation library to calculate position then compare to location from
+ * Location Manager.
+ */
+ @CddTest(requirement = "7.3.3")
+ public void testPseudoPosition() throws Exception {
+ // Checks if Gnss hardware feature is present, skips test (pass) if not,
+ // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
+ // From android O, CTS tests should run in the lab with GPS signal.
+ if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
+ TAG, MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED, true)) {
+ return;
+ }
+
+ mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+ mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+ mMeasurementListener = new TestGnssMeasurementListener(TAG,
+ MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
+ mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+ boolean success = mLocationListener.await();
+
+ List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
+ assertTrue("Time elapsed without getting enough location fixes."
+ + " Possibly, the test has been run deep indoors."
+ + " Consider retrying test outdoors.",
+ success && receivedLocationList.size() > 0);
+ Location locationFromApi = receivedLocationList.get(0);
+
+ // Since we are checking the eventCount later, there is no need to check the return value
+ // here.
+ mMeasurementListener.await();
+
+ List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+ int eventCount = events.size();
+ Log.i(TAG, "Number of Gps Event received = " + eventCount);
+ int gnssYearOfHardware = mTestLocationManager.getLocationManager().getGnssYearOfHardware();
+ if (eventCount == 0 && gnssYearOfHardware < MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED) {
+ return;
+ }
+
+ Log.i(TAG, "This is a device from 2016 or later.");
+ assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
+ eventCount > 0);
+
+ PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
+ = new PseudorangePositionVelocityFromRealTimeEvents();
+ mPseudorangePositionFromRealTimeEvents.setReferencePosition(
+ (int) (locationFromApi.getLatitude() * 1E7),
+ (int) (locationFromApi.getLongitude() * 1E7),
+ (int) (locationFromApi.getAltitude() * 1E7));
+
+ Log.i(TAG, "Location from Location Manager"
+ + ", Latitude:" + locationFromApi.getLatitude()
+ + ", Longitude:" + locationFromApi.getLongitude()
+ + ", Altitude:" + locationFromApi.getAltitude());
+
+ // Ensure at least some calculated locations have a reasonably low uncertainty
+ boolean someLocationsHaveLowPosUnc = false;
+ boolean someLocationsHaveLowVelUnc = false;
+
+ int totalCalculatedLocationCnt = 0;
+ for (GnssMeasurementsEvent event : events) {
+ // In mMeasurementListener.getEvents() we already filtered out events, at this point
+ // every event will have at least 4 satellites in one constellation.
+ mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(
+ event);
+ double[] calculatedLatLngAlt =
+ mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
+ // it will return NaN when there is no enough measurements to calculate the position
+ if (Double.isNaN(calculatedLatLngAlt[0])) {
+ continue;
+ } else {
+ totalCalculatedLocationCnt++;
+ Log.i(TAG, "Calculated Location"
+ + ", Latitude:" + calculatedLatLngAlt[0]
+ + ", Longitude:" + calculatedLatLngAlt[1]
+ + ", Altitude:" + calculatedLatLngAlt[2]);
+
+ double[] posVelUncertainties =
+ mPseudorangePositionFromRealTimeEvents.getPositionVelocityUncertaintyEnu();
+
+ double horizontalPositionUncertaintyMeters =
+ Math.sqrt(posVelUncertainties[0] * posVelUncertainties[0]
+ + posVelUncertainties[1] * posVelUncertainties[1]);
+
+ // Tolerate large offsets, when the device reports a large uncertainty - while also
+ // ensuring (here) that some locations are produced before the test ends
+ // with a reasonably low set of error estimates
+ if (horizontalPositionUncertaintyMeters < LOW_ENOUGH_POSITION_UNCERTAINTY_METERS) {
+ someLocationsHaveLowPosUnc = true;
+ }
+
+ // Root-sum-sqaure the WLS, and device generated 68%ile accuracies is a conservative
+ // 68%ile offset (given likely correlated errors) - then this is scaled by
+ // initially 3 sigma to give a high enough tolerance to make the test tolerant
+ // enough of noise to pass reliably. Floor adds additional robustness in case of
+ // small errors and small error estimates.
+ double horizontalOffsetThresholdMeters = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+ horizontalPositionUncertaintyMeters * horizontalPositionUncertaintyMeters
+ + locationFromApi.getAccuracy() * locationFromApi.getAccuracy())
+ + HORIZONTAL_OFFSET_FLOOR_METERS;
+
+ Location calculatedLocation = new Location("gps");
+ calculatedLocation.setLatitude(calculatedLatLngAlt[0]);
+ calculatedLocation.setLongitude(calculatedLatLngAlt[1]);
+ calculatedLocation.setAltitude(calculatedLatLngAlt[2]);
+
+ double horizontalOffset = calculatedLocation.distanceTo(locationFromApi);
+
+ Log.i(TAG, "Calculated Location Offset: " + horizontalOffset
+ + ", Threshold: " + horizontalOffsetThresholdMeters);
+ assertTrue("Latitude & Longitude calculated from pseudoranges should be close to "
+ + "those reported from Location Manager. Offset = "
+ + horizontalOffset + " meters. Threshold = "
+ + horizontalOffsetThresholdMeters + " meters ",
+ horizontalOffset < horizontalOffsetThresholdMeters);
+
+ //TODO: Check for the altitude offset
+
+ // This 2D velocity uncertainty is conservatively larger than speed uncertainty
+ // as it also contains the effect of bearing uncertainty at a constant speed
+ double horizontalVelocityUncertaintyMps =
+ Math.sqrt(posVelUncertainties[4] * posVelUncertainties[4]
+ + posVelUncertainties[5] * posVelUncertainties[5]);
+ if (horizontalVelocityUncertaintyMps < LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS) {
+ someLocationsHaveLowVelUnc = true;
+ }
+
+ // Assume 1m/s uncertainty from API, for this test, if not provided
+ float speedUncFromApiMps = locationFromApi.hasSpeedAccuracy()
+ ? locationFromApi.getSpeedAccuracyMetersPerSecond()
+ : HORIZONTAL_OFFSET_FLOOR_MPS;
+
+ // Similar 3-standard deviation plus floor threshold as
+ // horizontalOffsetThresholdMeters above
+ double horizontalSpeedOffsetThresholdMps = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+ horizontalVelocityUncertaintyMps * horizontalVelocityUncertaintyMps
+ + speedUncFromApiMps * speedUncFromApiMps)
+ + HORIZONTAL_OFFSET_FLOOR_MPS;
+
+ double[] calculatedVelocityEnuMps =
+ mPseudorangePositionFromRealTimeEvents.getVelocitySolutionEnuMps();
+ double calculatedHorizontalSpeedMps =
+ Math.sqrt(calculatedVelocityEnuMps[0] * calculatedVelocityEnuMps[0]
+ + calculatedVelocityEnuMps[1] * calculatedVelocityEnuMps[1]);
+
+ Log.i(TAG, "Calculated Speed: " + calculatedHorizontalSpeedMps
+ + ", Reported Speed: " + locationFromApi.getSpeed()
+ + ", Threshold: " + horizontalSpeedOffsetThresholdMps);
+ assertTrue("Speed (" + calculatedHorizontalSpeedMps + " m/s) calculated from"
+ + " pseudoranges should be close to the speed ("
+ + locationFromApi.getSpeed() + " m/s) reported from"
+ + " Location Manager.",
+ Math.abs(calculatedHorizontalSpeedMps - locationFromApi.getSpeed())
+ < horizontalSpeedOffsetThresholdMps);
+ }
+ }
+
+ assertTrue("Calculated Location Count should be greater than 0.",
+ totalCalculatedLocationCnt > 0);
+ assertTrue("Calculated Horizontal Location Uncertainty should at least once be less than "
+ + LOW_ENOUGH_POSITION_UNCERTAINTY_METERS,
+ someLocationsHaveLowPosUnc);
+ assertTrue("Calculated Horizontal Velocity Uncertainty should at least once be less than "
+ + LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS,
+ someLocationsHaveLowVelUnc);
}
-
- mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
- mTestLocationManager.requestLocationUpdates(mLocationListener);
-
- mMeasurementListener = new TestGnssMeasurementListener(TAG,
- MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
- mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
- boolean success = mLocationListener.await();
-
- List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
- assertTrue("Time elapsed without getting enough location fixes."
- + " Possibly, the test has been run deep indoors."
- + " Consider retrying test outdoors.",
- success && receivedLocationList.size() > 0);
- Location locationFromApi = receivedLocationList.get(0);
-
- // Since we are checking the eventCount later, there is no need to check the return value here.
- mMeasurementListener.await();
-
- List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
- int eventCount = events.size();
- Log.i(TAG, "Number of Gps Event received = " + eventCount);
- int gnssYearOfHardware = mTestLocationManager.getLocationManager().getGnssYearOfHardware();
- if (eventCount == 0 && gnssYearOfHardware < MIN_HARDWARE_YEAR_MEASUREMENTS_REQUIRED) {
- return;
- }
-
- Log.i(TAG, "This is a device from 2016 or later.");
- assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
- eventCount > 0);
-
- PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
- = new PseudorangePositionVelocityFromRealTimeEvents();
- mPseudorangePositionFromRealTimeEvents.setReferencePosition(
- (int) (locationFromApi.getLatitude() * 1E7),
- (int) (locationFromApi.getLongitude() * 1E7),
- (int) (locationFromApi.getAltitude() * 1E7));
-
- Log.i(TAG, "Location from Location Manager"
- + ", Latitude:" + locationFromApi.getLatitude()
- + ", Longitude:" + locationFromApi.getLongitude()
- + ", Altitude:" + locationFromApi.getAltitude());
-
-
- int totalCalculatedLocationCnt = 0;
- for(GnssMeasurementsEvent event : events){
- // In mMeasurementListener.getEvents() we already filtered out events, at this point every
- // event will have at least 4 satellites in one constellation.
- mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(event);
- double[] calculatedLocation =
- mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
- // it will return NaN when there is no enough measurements to calculate the position
- if (Double.isNaN(calculatedLocation[0])) {
- continue;
- }
- else {
- totalCalculatedLocationCnt ++;
- Log.i(TAG, "Calculated Location"
- + ", Latitude:" + calculatedLocation[0]
- + ", Longitude:" + calculatedLocation[1]
- + ", Altitude:" + calculatedLocation[2]);
-
- assertTrue("Latitude should be close to " + locationFromApi.getLatitude(),
- Math.abs(calculatedLocation[0] - locationFromApi.getLatitude())
- < POSITION_THRESHOLD_IN_DEGREES);
- assertTrue("Longitude should be close to" + locationFromApi.getLongitude(),
- Math.abs(calculatedLocation[1] - locationFromApi.getLongitude())
- < POSITION_THRESHOLD_IN_DEGREES);
- //TODO: Check for the altitude and position uncertainty.
- }
- }
- assertTrue("Calculated Location Count should be greater than 0.",
- totalCalculatedLocationCnt > 0);
- }
}
diff --git a/tests/tests/location/src/android/location/cts/MmsPduProvider.java b/tests/tests/location/src/android/location/cts/MmsPduProvider.java
new file mode 100644
index 0000000..76d859e
--- /dev/null
+++ b/tests/tests/location/src/android/location/cts/MmsPduProvider.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * A simple provider to send MMS PDU to platform MMS service
+ */
+public class MmsPduProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ // Not supported
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // Not supported
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ // Not supported
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // Not supported
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ // Not supported
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String fileMode) throws FileNotFoundException {
+ File file = new File(getContext().getCacheDir(), uri.getPath());
+ int mode = (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
+ ParcelFileDescriptor.MODE_WRITE_ONLY
+ |ParcelFileDescriptor.MODE_TRUNCATE
+ |ParcelFileDescriptor.MODE_CREATE);
+ return ParcelFileDescriptor.open(file, mode);
+ }
+}
diff --git a/tests/tests/location/src/android/location/cts/TestLocationManager.java b/tests/tests/location/src/android/location/cts/TestLocationManager.java
index 390d450..370999b 100644
--- a/tests/tests/location/src/android/location/cts/TestLocationManager.java
+++ b/tests/tests/location/src/android/location/cts/TestLocationManager.java
@@ -105,11 +105,11 @@
*
* @param locationListener location listener for request
*/
- public void requestLocationUpdates(LocationListener locationListener) {
+ public void requestLocationUpdates(LocationListener locationListener, int minTimeMsec) {
if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
Log.i(TAG, "Request Location updates.");
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
- 0 /* minTime*/,
+ minTimeMsec,
0 /* minDistance */,
locationListener,
Looper.getMainLooper());
@@ -117,6 +117,15 @@
}
/**
+ * See {@code LocationManager#requestLocationUpdates}.
+ *
+ * @param locationListener location listener for request
+ */
+ public void requestLocationUpdates(LocationListener locationListener) {
+ requestLocationUpdates(locationListener, 0 /* minTimeMsec */);
+ }
+
+ /**
* See {@code LocationManager#requestNetworkLocationUpdates}.
*
* @param locationListener location listener for request
diff --git a/tests/tests/location2/Android.mk b/tests/tests/location2/Android.mk
index 5b9f327..08993d5 100644
--- a/tests/tests/location2/Android.mk
+++ b/tests/tests/location2/Android.mk
@@ -24,7 +24,9 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner junit
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/location2/AndroidTest.xml b/tests/tests/location2/AndroidTest.xml
index 3443098..412fd1d 100644
--- a/tests/tests/location2/AndroidTest.xml
+++ b/tests/tests/location2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Location test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="location" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index e03eb76..75c1f07 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -51,7 +51,6 @@
ctstestrunner \
ctstestserver \
junit \
- legacy-android-test \
ndkaudio \
testng
@@ -75,6 +74,7 @@
#LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES += android.test.runner org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES += android.test.base
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 482cd6a..44af14d 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -30,7 +30,7 @@
<uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
<uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" />
- <application>
+ <application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index f7d06b1..e9ecdd3 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Media test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
<option name="images-only" value="true" />
@@ -29,6 +30,8 @@
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.media.cts" />
+ <!-- setup can be expensive so limit the number of shards -->
+ <option name="ajur-max-shard" value="5" />
<!-- test-timeout unit is ms, value = 30 min -->
<option name="test-timeout" value="1800000" />
<option name="runtime-hint" value="4h" />
diff --git a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
index cff5c18..d1e63ec 100644
--- a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
@@ -64,6 +64,8 @@
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
};
+// The test content is not packaged with clearkey UUID,
+// we have to use a canned clearkey pssh for the test.
static const uint8_t kClearkeyPssh[] = {
// BMFF box header (4 bytes size + 'pssh')
0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
diff --git a/tests/tests/media/res/raw/testmp3_4.mp3 b/tests/tests/media/res/raw/testmp3_4.mp3
new file mode 100755
index 0000000..2098ebd
--- /dev/null
+++ b/tests/tests/media/res/raw/testmp3_4.mp3
Binary files differ
diff --git a/tests/tests/media/res/values/strings.xml b/tests/tests/media/res/values/strings.xml
index b01b8ec..018ab70 100644
--- a/tests/tests/media/res/values/strings.xml
+++ b/tests/tests/media/res/values/strings.xml
@@ -16,4 +16,5 @@
<resources>
<string name="test_user_route_name">User route\'s name for test</string>
<string name="test_route_category_name">Route category\'s name for test</string>
+ <string name="test_localizable_title">Translatable Ringtone Title</string>
</resources>
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index ab7e4b6..775fb35 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -38,8 +38,12 @@
import android.app.ActivityManager;
import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Resources;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.content.pm.PackageManager;
import android.media.AudioSystem;
@@ -144,6 +148,73 @@
assertFalse(mAudioManager.isMicrophoneMute());
}
+ public void testMicrophoneMuteIntent() throws Exception {
+ final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver();
+ final boolean initialMicMute = mAudioManager.isMicrophoneMute();
+ try {
+ mContext.registerReceiver(receiver,
+ new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
+ // change the mic mute state
+ mAudioManager.setMicrophoneMute(!initialMicMute);
+ // verify a change was reported
+ final boolean intentFired = receiver.waitForMicMuteChanged(500/*ms*/);
+ assertTrue("ACTION_MICROPHONE_MUTE_CHANGED wasn't fired", intentFired);
+ // verify the mic mute state is expected
+ final boolean newMicMute = mAudioManager.isMicrophoneMute();
+ assertTrue("new mic mute state not as expected (" + !initialMicMute + ")",
+ newMicMute == !initialMicMute);
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ mAudioManager.setMicrophoneMute(initialMicMute);
+ }
+ }
+
+ // helper class to simplify that abstracts out the handling of spurious wakeups in Object.wait()
+ private static final class SafeWaitObject {
+ private boolean mQuit = false;
+
+ public void safeNotify() {
+ synchronized (this) {
+ mQuit = true;
+ this.notify();
+ }
+ }
+
+ public void safeWait(long millis) throws InterruptedException {
+ final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
+ synchronized (this) {
+ while (!mQuit) {
+ final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
+ if (timeToWait < 0) { break; }
+ this.wait(timeToWait);
+ }
+ }
+ }
+ }
+
+ private static final class MyBlockingIntentReceiver extends BroadcastReceiver {
+ private final SafeWaitObject mLock = new SafeWaitObject();
+ // state protected by mLock
+ private boolean mIntentReceived = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mLock) {
+ mIntentReceived = true;
+ mLock.safeNotify();
+ }
+ }
+
+ public boolean waitForMicMuteChanged(long timeOutMs) {
+ synchronized (mLock) {
+ try {
+ mLock.safeWait(timeOutMs);
+ } catch (InterruptedException e) { }
+ return mIntentReceived;
+ }
+ }
+ }
+
public void testSoundEffects() throws Exception {
Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
@@ -176,6 +247,29 @@
mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT, volume);
}
+ public void testCheckingZenModeBlockDoesNotRequireNotificationPolicyAccess() throws Exception {
+ try {
+ // set zen mode to priority only, so playSoundEffect will check notification policy
+ Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+ true);
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
+
+ // take away write-notification policy access from the package
+ Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+ false);
+
+ // playSoundEffect should NOT throw a security exception; all apps have read-access
+ mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
+ } finally {
+ Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+ true);
+ setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+ Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+ false);
+ }
+ }
+
public void testMusicActive() throws Exception {
if (mAudioManager.isMusicActive()) {
return;
@@ -967,6 +1061,93 @@
mAudioManager.adjustVolume(37, 0);
}
+ private final int[] PUBLIC_STREAM_TYPES = { AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING, AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_DTMF, AudioManager.STREAM_ACCESSIBILITY };
+
+ public void testGetStreamVolumeDbWithIllegalArguments() throws Exception {
+ Exception ex = null;
+ // invalid stream type
+ try {
+ float gain = mAudioManager.getStreamVolumeDb(-100 /*streamType*/, 0,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ } catch (Exception e) {
+ ex = e; // expected
+ }
+ assertNotNull("No exception was thrown for an invalid stream type", ex);
+ assertEquals("Wrong exception thrown for invalid stream type",
+ ex.getClass(), IllegalArgumentException.class);
+
+ // invalid volume index
+ ex = null;
+ try {
+ float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, -101 /*volume*/,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ } catch (Exception e) {
+ ex = e; // expected
+ }
+ assertNotNull("No exception was thrown for an invalid volume index", ex);
+ assertEquals("Wrong exception thrown for invalid volume index",
+ ex.getClass(), IllegalArgumentException.class);
+
+ // invalid out of range volume index
+ ex = null;
+ try {
+ final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, maxVol + 1,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ } catch (Exception e) {
+ ex = e; // expected
+ }
+ assertNotNull("No exception was thrown for an invalid out of range volume index", ex);
+ assertEquals("Wrong exception thrown for invalid out of range volume index",
+ ex.getClass(), IllegalArgumentException.class);
+
+ // invalid device type
+ ex = null;
+ try {
+ float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+ -102 /*deviceType*/);
+ } catch (Exception e) {
+ ex = e; // expected
+ }
+ assertNotNull("No exception was thrown for an invalid device type", ex);
+ assertEquals("Wrong exception thrown for invalid device type",
+ ex.getClass(), IllegalArgumentException.class);
+
+ // invalid input device type
+ ex = null;
+ try {
+ float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+ AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ } catch (Exception e) {
+ ex = e; // expected
+ }
+ assertNotNull("No exception was thrown for an invalid input device type", ex);
+ assertEquals("Wrong exception thrown for invalid input device type",
+ ex.getClass(), IllegalArgumentException.class);
+ }
+
+ public void testGetStreamVolumeDb() throws Exception {
+ for (int streamType : PUBLIC_STREAM_TYPES) {
+ // verify mininum index is strictly inferior to maximum index
+ final int minIndex = mAudioManager.getStreamMinVolume(streamType);
+ final int maxIndex = mAudioManager.getStreamMaxVolume(streamType);
+ assertTrue("Min vol index (" + minIndex + ") for stream " + streamType + " not inferior"
+ + " to max vol index (" + maxIndex + ")", minIndex <= maxIndex);
+ float prevGain = Float.NEGATIVE_INFINITY;
+ // verify gain increases with the volume indices
+ for (int idx = minIndex ; idx <= maxIndex ; idx++) {
+ float gain = mAudioManager.getStreamVolumeDb(streamType, idx,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ assertTrue("Non-monotonically increasing gain at index " + idx + " for stream"
+ + streamType, prevGain <= gain);
+ prevGain = gain;
+ }
+ }
+ }
+
public void testAdjustSuggestedStreamVolumeWithIllegalArguments() throws Exception {
// Call the method with illegal direction. System should not reboot.
mAudioManager.adjustSuggestedStreamVolume(37, AudioManager.STREAM_MUSIC, 0);
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index e5f9385..f3c17cf 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -145,11 +145,11 @@
final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid");
final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType");
try {
- Integer uid = (Integer) getClientUidMethod.invoke(config, null);
+ Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue());
- Integer pid = (Integer) getClientPidMethod.invoke(config, null);
+ Integer pid = (Integer) getClientPidMethod.invoke(config, (Object[]) null);
assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue());
- Integer type = (Integer) getPlayerTypeMethod.invoke(config, null);
+ Integer type = (Integer) getPlayerTypeMethod.invoke(config, (Object[]) null);
assertEquals("player type isn't protected", -1 /*expected*/, type.intValue());
} catch (Exception e) {
fail("Exception thrown during reflection on config privileged fields"+ e);
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 8fd1b7a..98364ec 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -251,6 +251,13 @@
AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT);
}
+ public void testAudioRecordLocalMono16BitShort() throws Exception {
+ doTest("local_mono_16bit_short", true /*localRecord*/, false /*customHandler*/,
+ 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
+ false /*useByteBuffer*/, true /*blocking*/,
+ false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
+ AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 500 /*TEST_TIME_MS*/);
+ }
public void testAudioRecordLocalMono16Bit() throws Exception {
doTest("local_mono_16bit", true /*localRecord*/, false /*customHandler*/,
30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
@@ -743,11 +750,21 @@
boolean useByteBuffer, boolean blocking,
final boolean auditRecording, final boolean isChannelIndex,
final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception {
+ final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
+ doTest(reportName, localRecord, customHandler, periodsPerSecond, markerPeriodsPerSecond,
+ useByteBuffer, blocking, auditRecording, isChannelIndex,
+ TEST_SR, TEST_CONF, TEST_FORMAT, TEST_TIME_MS);
+ }
+ private void doTest(String reportName, boolean localRecord, boolean customHandler,
+ int periodsPerSecond, int markerPeriodsPerSecond,
+ boolean useByteBuffer, boolean blocking,
+ final boolean auditRecording, final boolean isChannelIndex,
+ final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT, final int TEST_TIME_MS)
+ throws Exception {
if (!hasMicrophone()) {
return;
}
// audit recording plays back recorded audio, so use longer test timing
- final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
final int TEST_SOURCE = MediaRecorder.AudioSource.DEFAULT;
mIsHandleMessageCalled = false;
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
index 068087d..eb7296f 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
@@ -319,9 +319,9 @@
try {
final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
final Method getClientPackageName = confClass.getDeclaredMethod("getClientPackageName");
- Integer uid = (Integer) getClientUidMethod.invoke(config, null);
+ Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
assertEquals("client uid isn't protected", -1 /*expected*/, uid.intValue());
- String name = (String) getClientPackageName.invoke(config, null);
+ String name = (String) getClientPackageName.invoke(config, (Object[]) null);
assertNotNull("client package name is null", name);
assertEquals("client package name isn't protected", 0 /*expected*/, name.length());
} catch (Exception e) {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 3d853d4..23ec510 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -1461,6 +1461,30 @@
track.release();
}
+ public void testPlayStaticDataShort() throws Exception {
+ if (!hasAudioOutput()) {
+ Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+ + "audio output HAL");
+ return;
+ }
+ // constants for test
+ final String TEST_NAME = "testPlayStaticDataShort";
+ final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
+ final int TEST_SR = 48000;
+ final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
+ final int TEST_MODE = AudioTrack.MODE_STATIC;
+ final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+ final double TEST_SWEEP = 100;
+ final int TEST_LOOPS = 1;
+ final double TEST_FREQUENCY = 400;
+ final long NO_WAIT = 0;
+ final double TEST_LOOP_DURATION = 0.25;
+
+ playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+ TEST_LOOPS, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF, NO_WAIT, TEST_LOOP_DURATION);
+
+ }
+
public void testPlayStaticData() throws Exception {
if (!hasAudioOutput()) {
Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
@@ -1487,90 +1511,117 @@
final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
final double TEST_SWEEP = 100;
final int TEST_LOOPS = 1;
+ final double TEST_LOOP_DURATION=1;
for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
double frequency = 400; // frequency changes for each test
for (int TEST_SR : TEST_SR_ARRAY) {
for (int TEST_CONF : TEST_CONF_ARRAY) {
- // -------- initialization --------------
- final int seconds = 1;
- final int channelCount = Integer.bitCount(TEST_CONF);
- final int bufferFrames = seconds * TEST_SR;
- final int bufferSamples = bufferFrames * channelCount;
- final int bufferSize = bufferSamples
- * AudioFormat.getBytesPerSample(TEST_FORMAT);
- final double testFrequency = frequency / channelCount;
- final long MILLISECONDS_PER_SECOND = 1000;
- AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR,
- TEST_CONF, TEST_FORMAT, bufferSize, TEST_MODE);
- assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+ playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+ TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_MSEC, TEST_LOOP_DURATION);
- // -------- test --------------
-
- // test setLoopPoints and setPosition can be called here.
- assertEquals(TEST_NAME,
- android.media.AudioTrack.SUCCESS,
- track.setPlaybackHeadPosition(bufferFrames/2));
- assertEquals(TEST_NAME,
- android.media.AudioTrack.SUCCESS,
- track.setLoopPoints(
- 0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/));
- // only need to write once to the static track
- switch (TEST_FORMAT) {
- case AudioFormat.ENCODING_PCM_8BIT: {
- byte data[] = AudioHelper.createSoundDataInByteArray(
- bufferSamples, TEST_SR,
- testFrequency, TEST_SWEEP);
- assertEquals(TEST_NAME,
- bufferSamples,
- track.write(data, 0 /*offsetInBytes*/, data.length));
- } break;
- case AudioFormat.ENCODING_PCM_16BIT: {
- short data[] = AudioHelper.createSoundDataInShortArray(
- bufferSamples, TEST_SR,
- testFrequency, TEST_SWEEP);
- assertEquals(TEST_NAME,
- bufferSamples,
- track.write(data, 0 /*offsetInBytes*/, data.length));
- } break;
- case AudioFormat.ENCODING_PCM_FLOAT: {
- float data[] = AudioHelper.createSoundDataInFloatArray(
- bufferSamples, TEST_SR,
- testFrequency, TEST_SWEEP);
- assertEquals(TEST_NAME,
- bufferSamples,
- track.write(data, 0 /*offsetInBytes*/, data.length,
- AudioTrack.WRITE_BLOCKING));
- } break;
- }
- assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
- // test setLoopPoints and setPosition can be called here.
- assertEquals(TEST_NAME,
- android.media.AudioTrack.SUCCESS,
- track.setPlaybackHeadPosition(0 /*positionInFrames*/));
- assertEquals(TEST_NAME,
- android.media.AudioTrack.SUCCESS,
- track.setLoopPoints(0 /*startInFrames*/, bufferFrames, TEST_LOOPS));
-
- track.play();
- Thread.sleep(seconds * MILLISECONDS_PER_SECOND * (TEST_LOOPS + 1));
- Thread.sleep(WAIT_MSEC);
-
- // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
- // the running count of frames played, not the actual static buffer position.
- int position = track.getPlaybackHeadPosition();
- assertEquals(TEST_NAME, bufferFrames * (TEST_LOOPS + 1), position);
-
- track.stop();
- Thread.sleep(WAIT_MSEC);
- // -------- tear down --------------
- track.release();
frequency += 70; // increment test tone frequency
}
}
}
}
+ private void playOnceStaticData(String testName, int testMode, int testStreamType,
+ double testSweep, int testLoops, int testFormat, double testFrequency, int testSr,
+ int testConf, long waitMsec, double testLoopDuration)
+ throws InterruptedException {
+ // -------- initialization --------------
+ final int channelCount = Integer.bitCount(testConf);
+ final int bufferFrames = (int)(testLoopDuration * testSr);
+ final int bufferSamples = bufferFrames * channelCount;
+ final int bufferSize = bufferSamples
+ * AudioFormat.getBytesPerSample(testFormat);
+ final double frequency = testFrequency / channelCount;
+ final long MILLISECONDS_PER_SECOND = 1000;
+ AudioTrack track = new AudioTrack(testStreamType, testSr,
+ testConf, testFormat, bufferSize, testMode);
+ assertEquals(testName, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+
+ // -------- test --------------
+
+ // test setLoopPoints and setPosition can be called here.
+ assertEquals(testName,
+ android.media.AudioTrack.SUCCESS,
+ track.setPlaybackHeadPosition(bufferFrames/2));
+ assertEquals(testName,
+ android.media.AudioTrack.SUCCESS,
+ track.setLoopPoints(
+ 0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/));
+ // only need to write once to the static track
+ switch (testFormat) {
+ case AudioFormat.ENCODING_PCM_8BIT: {
+ byte data[] = AudioHelper.createSoundDataInByteArray(
+ bufferSamples, testSr,
+ frequency, testSweep);
+ assertEquals(testName,
+ bufferSamples,
+ track.write(data, 0 /*offsetInBytes*/, data.length));
+ } break;
+ case AudioFormat.ENCODING_PCM_16BIT: {
+ short data[] = AudioHelper.createSoundDataInShortArray(
+ bufferSamples, testSr,
+ frequency, testSweep);
+ assertEquals(testName,
+ bufferSamples,
+ track.write(data, 0 /*offsetInBytes*/, data.length));
+ } break;
+ case AudioFormat.ENCODING_PCM_FLOAT: {
+ float data[] = AudioHelper.createSoundDataInFloatArray(
+ bufferSamples, testSr,
+ frequency, testSweep);
+ assertEquals(testName,
+ bufferSamples,
+ track.write(data, 0 /*offsetInBytes*/, data.length,
+ AudioTrack.WRITE_BLOCKING));
+ } break;
+ }
+ assertEquals(testName, AudioTrack.STATE_INITIALIZED, track.getState());
+ // test setLoopPoints and setPosition can be called here.
+ assertEquals(testName,
+ android.media.AudioTrack.SUCCESS,
+ track.setPlaybackHeadPosition(0 /*positionInFrames*/));
+ assertEquals(testName,
+ android.media.AudioTrack.SUCCESS,
+ track.setLoopPoints(0 /*startInFrames*/, bufferFrames, testLoops));
+
+ track.play();
+ Thread.sleep((int)(testLoopDuration * MILLISECONDS_PER_SECOND) * (testLoops + 1));
+ Thread.sleep(waitMsec);
+
+ // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
+ // the running count of frames played, not the actual static buffer position.
+ int position = track.getPlaybackHeadPosition();
+ assertEquals(testName, bufferFrames * (testLoops + 1), position);
+
+ track.stop();
+ Thread.sleep(waitMsec);
+ // -------- tear down --------------
+ track.release();
+ }
+
+ public void testPlayStreamDataShort() throws Exception {
+ // constants for test
+ final String TEST_NAME = "testPlayStreamDataShort";
+ final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+ final int TEST_SR = 48000;
+ final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+ final int TEST_MODE = AudioTrack.MODE_STREAM;
+ final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+ final float TEST_SWEEP = 0; // sine wave only
+ final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
+ final double TEST_FREQUENCY = 1000;
+ final long NO_WAIT = 0;
+
+ playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+ TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF,
+ NO_WAIT);
+ }
+
public void testPlayStreamData() throws Exception {
// constants for test
final String TEST_NAME = "testPlayStreamData";
@@ -1607,94 +1658,105 @@
double frequency = 400; // frequency changes for each test
for (int TEST_SR : TEST_SR_ARRAY) {
for (int TEST_CONF : TEST_CONF_ARRAY) {
- final int channelCount = Integer.bitCount(TEST_CONF);
- if (TEST_IS_LOW_RAM_DEVICE
- && (TEST_SR > 96000 || channelCount > 4)) {
- continue; // ignore. FIXME: reenable when AF memory allocation is updated.
- }
- // -------- initialization --------------
- final int minBufferSize = AudioTrack.getMinBufferSize(TEST_SR,
- TEST_CONF, TEST_FORMAT); // in bytes
- AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR,
- TEST_CONF, TEST_FORMAT, minBufferSize, TEST_MODE);
- assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-
- // compute parameters for the source signal data.
- AudioFormat format = track.getFormat();
- assertEquals(TEST_NAME, TEST_SR, format.getSampleRate());
- assertEquals(TEST_NAME, TEST_CONF, format.getChannelMask());
- assertEquals(TEST_NAME, channelCount, format.getChannelCount());
- assertEquals(TEST_NAME, TEST_FORMAT, format.getEncoding());
- final int sourceSamples = channelCount
- * AudioHelper.frameCountFromMsec(500,
- format); // duration of test tones
- final double testFrequency = frequency / channelCount;
-
- int written = 0;
- // For streaming tracks, it's ok to issue the play() command
- // before any audio is written.
- track.play();
- // -------- test --------------
-
- // samplesPerWrite can be any positive value.
- // We prefer this to be a multiple of channelCount so write()
- // does not return a short count.
- // If samplesPerWrite is very large, it is limited to the data length
- // and we simply write (blocking) the entire source data and not even loop.
- // We choose a value here which simulates double buffer writes.
- final int buffers = 2; // double buffering mode
- final int samplesPerWrite =
- (track.getBufferSizeInFrames() / buffers) * channelCount;
- switch (TEST_FORMAT) {
- case AudioFormat.ENCODING_PCM_8BIT: {
- byte data[] = AudioHelper.createSoundDataInByteArray(
- sourceSamples, TEST_SR,
- testFrequency, TEST_SWEEP);
- while (written < data.length) {
- int samples = Math.min(data.length - written, samplesPerWrite);
- int ret = track.write(data, written, samples);
- assertEquals(TEST_NAME, samples, ret);
- written += ret;
- }
- } break;
- case AudioFormat.ENCODING_PCM_16BIT: {
- short data[] = AudioHelper.createSoundDataInShortArray(
- sourceSamples, TEST_SR,
- testFrequency, TEST_SWEEP);
- while (written < data.length) {
- int samples = Math.min(data.length - written, samplesPerWrite);
- int ret = track.write(data, written, samples);
- assertEquals(TEST_NAME, samples, ret);
- written += ret;
- }
- } break;
- case AudioFormat.ENCODING_PCM_FLOAT: {
- float data[] = AudioHelper.createSoundDataInFloatArray(
- sourceSamples, TEST_SR,
- testFrequency, TEST_SWEEP);
- while (written < data.length) {
- int samples = Math.min(data.length - written, samplesPerWrite);
- int ret = track.write(data, written, samples,
- AudioTrack.WRITE_BLOCKING);
- assertEquals(TEST_NAME, samples, ret);
- written += ret;
- }
- } break;
- }
-
- // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback.
- // Rather, it allows the remaining data in the internal buffer to drain.
- track.stop();
- Thread.sleep(WAIT_MSEC); // wait for the data to drain.
- // -------- tear down --------------
- track.release();
- Thread.sleep(WAIT_MSEC); // wait for release to complete
+ playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+ TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
+ WAIT_MSEC);
frequency += 50; // increment test tone frequency
}
}
}
}
+ private void playOnceStreamData(String testName, int testMode, int testStream,
+ float testSweep, boolean isLowRamDevice, int testFormat, double testFrequency,
+ int testSr, int testConf, long waitMsec) throws InterruptedException {
+ final int channelCount = Integer.bitCount(testConf);
+ if (isLowRamDevice
+ && (testSr > 96000 || channelCount > 4)) {
+ return; // ignore. FIXME: reenable when AF memory allocation is updated.
+ }
+ // -------- initialization --------------
+ final int minBufferSize = AudioTrack.getMinBufferSize(testSr,
+ testConf, testFormat); // in bytes
+ AudioTrack track = new AudioTrack(testStream, testSr,
+ testConf, testFormat, minBufferSize, testMode);
+ assertTrue(testName, track.getState() == AudioTrack.STATE_INITIALIZED);
+
+ // compute parameters for the source signal data.
+ AudioFormat format = track.getFormat();
+ assertEquals(testName, testSr, format.getSampleRate());
+ assertEquals(testName, testConf, format.getChannelMask());
+ assertEquals(testName, channelCount, format.getChannelCount());
+ assertEquals(testName, testFormat, format.getEncoding());
+ final int sourceSamples = channelCount
+ * AudioHelper.frameCountFromMsec(500,
+ format); // duration of test tones
+ final double frequency = testFrequency / channelCount;
+
+ int written = 0;
+ // For streaming tracks, it's ok to issue the play() command
+ // before any audio is written.
+ track.play();
+ // -------- test --------------
+
+ // samplesPerWrite can be any positive value.
+ // We prefer this to be a multiple of channelCount so write()
+ // does not return a short count.
+ // If samplesPerWrite is very large, it is limited to the data length
+ // and we simply write (blocking) the entire source data and not even loop.
+ // We choose a value here which simulates double buffer writes.
+ final int buffers = 2; // double buffering mode
+ final int samplesPerWrite =
+ (track.getBufferSizeInFrames() / buffers) * channelCount;
+ switch (testFormat) {
+ case AudioFormat.ENCODING_PCM_8BIT: {
+ byte data[] = AudioHelper.createSoundDataInByteArray(
+ sourceSamples, testSr,
+ frequency, testSweep);
+ while (written < data.length) {
+ int samples = Math.min(data.length - written, samplesPerWrite);
+ int ret = track.write(data, written, samples);
+ assertEquals(testName, samples, ret);
+ written += ret;
+ }
+ }
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT: {
+ short data[] = AudioHelper.createSoundDataInShortArray(
+ sourceSamples, testSr,
+ frequency, testSweep);
+ while (written < data.length) {
+ int samples = Math.min(data.length - written, samplesPerWrite);
+ int ret = track.write(data, written, samples);
+ assertEquals(testName, samples, ret);
+ written += ret;
+ }
+ }
+ break;
+ case AudioFormat.ENCODING_PCM_FLOAT: {
+ float data[] = AudioHelper.createSoundDataInFloatArray(
+ sourceSamples, testSr,
+ frequency, testSweep);
+ while (written < data.length) {
+ int samples = Math.min(data.length - written, samplesPerWrite);
+ int ret = track.write(data, written, samples,
+ AudioTrack.WRITE_BLOCKING);
+ assertEquals(testName, samples, ret);
+ written += ret;
+ }
+ }
+ break;
+ }
+
+ // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback.
+ // Rather, it allows the remaining data in the internal buffer to drain.
+ track.stop();
+ Thread.sleep(waitMsec); // wait for the data to drain.
+ // -------- tear down --------------
+ track.release();
+ Thread.sleep(waitMsec); // wait for release to complete
+ }
+
public void testPlayStreamByteBuffer() throws Exception {
// constants for test
final String TEST_NAME = "testPlayStreamByteBuffer";
@@ -2358,6 +2420,33 @@
assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
}
+ // Test AudioTrack to see if there are any problems with large frame counts.
+ public void testAudioTrackLargeFrameCount() throws Exception {
+ // constants for test
+ final String TEST_NAME = "testAudioTrackLargeFrameCount";
+ final int[] BUFFER_SIZES = { 4294968, 42949680, 429496800, Integer.MAX_VALUE };
+ final int[] MODES = { AudioTrack.MODE_STATIC, AudioTrack.MODE_STREAM };
+
+ for (int mode : MODES) {
+ for (int bufferSizeInBytes : BUFFER_SIZES) {
+ try {
+ final AudioTrack track = new AudioTrack.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+ .setSampleRate(44100)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+ .build())
+ .setTransferMode(mode)
+ .setBufferSizeInBytes(bufferSizeInBytes) // 1 byte == 1 frame
+ .build();
+ track.release(); // OK to successfully complete
+ } catch (UnsupportedOperationException e) {
+ ; // OK to throw unsupported exception
+ }
+ }
+ }
+ }
+
/* Do not run in JB-MR1. will be re-opened in the next platform release.
public void testResourceLeakage() throws Exception {
final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
index d43dce1..5afb71d 100644
--- a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
@@ -41,6 +41,7 @@
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.HashMap;
@@ -79,6 +80,18 @@
private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
private static final String MIME_VIDEO_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
+ // Property Keys
+ private static final String ALGORITHMS_PROPERTY_KEY = MediaDrm.PROPERTY_ALGORITHMS;
+ private static final String DESCRIPTION_PROPERTY_KEY = MediaDrm.PROPERTY_DESCRIPTION;
+ private static final String DEVICEID_PROPERTY_KEY = "deviceId";
+ private static final String INVALID_PROPERTY_KEY = "invalid property key";
+ private static final String LISTENER_TEST_SUPPORT_PROPERTY_KEY = "listenerTestSupport";
+ private static final String VENDOR_PROPERTY_KEY = MediaDrm.PROPERTY_VENDOR;
+ private static final String VERSION_PROPERTY_KEY = MediaDrm.PROPERTY_VERSION;
+
+ // Error message
+ private static final String ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED = "Crypto scheme is not supported";
+
private static final Uri CENC_AUDIO_URL = Uri.parse(
"https://storage.googleapis.com/wvmedia/clear/h264/llama/llama_aac_audio.mp4");
private static final Uri CENC_VIDEO_URL = Uri.parse(
@@ -100,7 +113,7 @@
private Looper mLooper;
private MediaCodecClearKeyPlayer mMediaCodecPlayer;
private MediaDrm mDrm;
- private Object mLock = new Object();
+ private final Object mLock = new Object();
private SurfaceHolder mSurfaceHolder;
@Override
@@ -348,7 +361,7 @@
drm = startDrm(clearKeys, initDataType, drmSchemeUuid);
if (!drm.isCryptoSchemeSupported(drmSchemeUuid)) {
stopDrm(drm);
- throw new Error("Crypto scheme is not supported.");
+ throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
}
mSessionId = openSession(drm);
}
@@ -435,7 +448,7 @@
MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc", COMMON_PSSH_SCHEME_UUID);
if (!drm.isCryptoSchemeSupported(COMMON_PSSH_SCHEME_UUID)) {
stopDrm(drm);
- throw new Error("Crypto scheme is not supported.");
+ throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
}
mSessionId = openSession(drm);
@@ -524,4 +537,156 @@
MPEG2TS_CLEAR_URL, false,
VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false);
}
+
+ private String getStringProperty(final MediaDrm drm, final String key) {
+ String value = "";
+ try {
+ value = drm.getPropertyString(key);
+ } catch (IllegalArgumentException e) {
+ // Expected exception for invalid key
+ Log.d(TAG, "Expected result: " + e.getMessage());
+ } catch (Exception e) {
+ throw new Error(e.getMessage() + "-" + key);
+ }
+ return value;
+ }
+
+ private byte[] getByteArrayProperty(final MediaDrm drm, final String key) {
+ byte[] bytes = new byte[0];
+ try {
+ bytes = drm.getPropertyByteArray(key);
+ } catch (IllegalArgumentException e) {
+ // Expected exception for invalid key
+ Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+ } catch (Exception e) {
+ throw new Error(e.getMessage() + "-" + key);
+ }
+ return bytes;
+ }
+
+ private void setStringProperty(final MediaDrm drm, final String key, final String value) {
+ try {
+ drm.setPropertyString(key, value);
+ } catch (IllegalArgumentException e) {
+ // Expected exception for invalid key
+ Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+ } catch (Exception e) {
+ throw new Error(e.getMessage() + "-" + key);
+ }
+ }
+
+ private void setByteArrayProperty(final MediaDrm drm, final String key, final byte[] bytes) {
+ try {
+ drm.setPropertyByteArray(key, bytes);
+ } catch (IllegalArgumentException e) {
+ // Expected exception for invalid key
+ Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+ } catch (Exception e) {
+ throw new Error(e.getMessage() + "-" + key);
+ }
+ }
+
+ public void testGetProperties() throws Exception {
+ MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
+ "cenc", COMMON_PSSH_SCHEME_UUID);
+
+ try {
+ // The following tests will not verify the value we are getting
+ // back since it could change in the future.
+ final String[] sKeys = {
+ DESCRIPTION_PROPERTY_KEY, LISTENER_TEST_SUPPORT_PROPERTY_KEY,
+ VENDOR_PROPERTY_KEY, VERSION_PROPERTY_KEY};
+ String value;
+ for (String key : sKeys) {
+ value = getStringProperty(drm, key);
+ Log.d(TAG, "getPropertyString returns: " + key + ", " + value);
+ if (value.isEmpty()) {
+ throw new Error("Failed to get property for: " + key);
+ }
+ }
+
+ byte[] bytes = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
+ if (0 == bytes.length) {
+ throw new Error("Failed to get property for: " + DEVICEID_PROPERTY_KEY);
+ }
+
+ // Test with an invalid property key.
+ value = getStringProperty(drm, INVALID_PROPERTY_KEY);
+ bytes = getByteArrayProperty(drm, INVALID_PROPERTY_KEY);
+ if (!value.isEmpty() || 0 != bytes.length) {
+ throw new Error("get property failed using an invalid property key");
+ }
+ } finally {
+ stopDrm(drm);
+ }
+ }
+
+ public void testSetProperties() throws Exception {
+ MediaDrm drm = startDrm(new byte[][]{CLEAR_KEY_CENC},
+ "cenc", COMMON_PSSH_SCHEME_UUID);
+
+ try {
+ // Test setting predefined string property
+ // - Save the value to be restored later
+ // - Set the property value
+ // - Check the value that was set
+ // - Restore previous value
+ String listenerTestSupport = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+
+ setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
+
+ String value = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+ if (!value.equals("testing")) {
+ throw new Error("Failed to set property: " + LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+ }
+
+ setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, listenerTestSupport);
+
+ // Test setting immutable properties
+ HashMap<String, String> defaultImmutableProperties = new HashMap<String, String>();
+ defaultImmutableProperties.put(ALGORITHMS_PROPERTY_KEY,
+ getStringProperty(drm, ALGORITHMS_PROPERTY_KEY));
+ defaultImmutableProperties.put(DESCRIPTION_PROPERTY_KEY,
+ getStringProperty(drm, DESCRIPTION_PROPERTY_KEY));
+ defaultImmutableProperties.put(VENDOR_PROPERTY_KEY,
+ getStringProperty(drm, VENDOR_PROPERTY_KEY));
+ defaultImmutableProperties.put(VERSION_PROPERTY_KEY,
+ getStringProperty(drm, VERSION_PROPERTY_KEY));
+
+ HashMap<String, String> immutableProperties = new HashMap<String, String>();
+ immutableProperties.put(ALGORITHMS_PROPERTY_KEY, "brute force");
+ immutableProperties.put(DESCRIPTION_PROPERTY_KEY, "testing only");
+ immutableProperties.put(VENDOR_PROPERTY_KEY, "my Google");
+ immutableProperties.put(VERSION_PROPERTY_KEY, "undefined");
+
+ for (String key : immutableProperties.keySet()) {
+ setStringProperty(drm, key, immutableProperties.get(key));
+ }
+
+ // Verify the immutable properties have not been set
+ for (String key : immutableProperties.keySet()) {
+ value = getStringProperty(drm, key);
+ if (!defaultImmutableProperties.get(key).equals(getStringProperty(drm, key))) {
+ throw new Error("Immutable property has changed, key=" + key);
+ }
+ }
+
+ // Test setPropertyByteArray for immutable property
+ final byte[] bytes = new byte[] {
+ 0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
+ 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
+
+ final byte[] deviceId = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
+
+ setByteArrayProperty(drm, DEVICEID_PROPERTY_KEY, bytes);
+
+ // Verify deviceId has not changed
+ if (!Arrays.equals(deviceId, getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY))) {
+ throw new Error("Failed to set byte array for key=" + DEVICEID_PROPERTY_KEY);
+ }
+ } finally {
+ stopDrm(drm);
+ }
+ }
+
}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
index fd8c3b1..c401c75 100644
--- a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
@@ -300,6 +300,24 @@
mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE_SIZE));
}
+ public void testSubscriptionCallbackNotCalledAfterDisconnect() {
+ createMediaBrowser(TEST_BROWSER_SERVICE);
+ connectMediaBrowserService();
+ mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+ mMediaBrowser.disconnect();
+ resetCallbacks();
+ StubMediaBrowserService.sInstance.notifyChildrenChanged(
+ StubMediaBrowserService.MEDIA_ID_ROOT);
+ try {
+ Thread.sleep(SLEEP_MS);
+ } catch (InterruptedException e) {
+ fail("Unexpected InterruptedException occurred.");
+ }
+ assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+ assertEquals(0, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+ assertNull(mSubscriptionCallback.mLastParentId);
+ }
+
public void testUnsubscribeForMultipleSubscriptions() {
createMediaBrowser(TEST_BROWSER_SERVICE);
connectMediaBrowserService();
@@ -436,6 +454,21 @@
assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mItemCallback.mLastErrorId);
}
+ public void testItemCallbackNotCalledAfterDisconnect() {
+ createMediaBrowser(TEST_BROWSER_SERVICE);
+ connectMediaBrowserService();
+ mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+ mMediaBrowser.disconnect();
+ resetCallbacks();
+ try {
+ Thread.sleep(SLEEP_MS);
+ } catch (InterruptedException e) {
+ fail("Unexpected InterruptedException occurred.");
+ }
+ assertNull(mItemCallback.mLastMediaItem);
+ assertNull(mItemCallback.mLastErrorId);
+ }
+
private void createMediaBrowser(final ComponentName component) {
getInstrumentation().runOnMainSync(new Runnable() {
@Override
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 363c350..7dd08c4 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -1846,6 +1846,14 @@
}
public void testChangeTimedTextTrack() throws Throwable {
+ testChangeTimedTextTrackWithSpeed(1.0f);
+ }
+
+ public void testChangeTimedTextTrackFast() throws Throwable {
+ testChangeTimedTextTrackWithSpeed(2.0f);
+ }
+
+ private void testChangeTimedTextTrackWithSpeed(float speed) throws Throwable {
testTimedText(R.raw.testvideo_with_2_timedtext_tracks, 2,
new int[] {R.raw.test_subtitle1_srt, R.raw.test_subtitle2_srt},
new VerifyAndSignalTimedText(),
@@ -1856,6 +1864,10 @@
mOnTimedTextCalled.reset();
mMediaPlayer.start();
+ if (speed != 1.0f) {
+ mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(speed));
+ }
+
assertTrue(mMediaPlayer.isPlaying());
// Waits until at least two subtitles are fired. Timeout is 2.5 sec.
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 137b7cf..401e2d5 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -110,6 +110,76 @@
"_data like ?", new String[] { mFileDir + "%"});
}
+ public void testLocalizeRingtoneTitles() throws Exception {
+ mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+ mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+ mMediaScannerConnectionClient);
+
+ assertFalse(mMediaScannerConnection.isConnected());
+
+ // start connection and wait until connected
+ mMediaScannerConnection.connect();
+ checkConnectionState(true);
+
+ // Write unlocalizable audio file and scan to insert into database
+ final String unlocalizablePath = mFileDir + "/unlocalizable.mp3";
+ writeFile(R.raw.testmp3, unlocalizablePath);
+ mMediaScannerConnection.scanFile(unlocalizablePath, null);
+ checkMediaScannerConnection();
+ final Uri media1Uri = mMediaScannerConnectionClient.mediaUri;
+
+ // Ensure unlocalizable titles come back correctly
+ final ContentResolver res = mContext.getContentResolver();
+ final String unlocalizedTitle = "Chimey Phone";
+ Cursor c = res.query(media1Uri, new String[] { "title" }, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(unlocalizedTitle, c.getString(0));
+
+ mMediaScannerConnectionClient.reset();
+
+ // Write localizable audio file and scan to insert into database
+ final String localizablePath = mFileDir + "/localizable.mp3";
+ writeFile(R.raw.testmp3_4, localizablePath);
+ mMediaScannerConnection.scanFile(localizablePath, null);
+ checkMediaScannerConnection();
+ final Uri media2Uri = mMediaScannerConnectionClient.mediaUri;
+
+ // Ensure localized title comes back localized
+ final String localizedTitle = mContext.getString(R.string.test_localizable_title);
+ c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(localizedTitle, c.getString(0));
+
+ // Update localizable audio file to have unlocalizable title
+ final ContentValues values = new ContentValues();
+ final String newTitle = "New Title";
+ values.put("title", newTitle);
+ res.update(media2Uri, values, null, null);
+
+ // Ensure title comes back correctly
+ c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(newTitle, c.getString(0));
+
+ // Update audio file to have localizable title once again
+ final String newLocalizableTitle =
+ "android.resource://android.media.cts/string/test_localizable_title";
+ values.put("title", newLocalizableTitle);
+ res.update(media2Uri, values, null, null);
+
+ // Ensure title comes back localized
+ c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ assertEquals(localizedTitle, c.getString(0));
+
+ mMediaScannerConnection.disconnect();
+ c.close();
+ }
+
public void testMediaScanner() throws InterruptedException, IOException {
mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index cc6f186..58be212 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -15,6 +15,8 @@
*/
package android.media.cts;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -33,6 +35,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
+import android.support.test.filters.LargeTest;
import android.test.AndroidTestCase;
import android.view.KeyEvent;
@@ -417,6 +420,27 @@
}
}
+ /**
+ * Tests {@link MediaSession#setCallback} with {@code null}. No callbacks will be called
+ * once {@code setCallback(null)} is done.
+ */
+ public void testSetCallbackWithNull() throws Exception {
+ MediaSessionCallback sessionCallback = new MediaSessionCallback();
+ mSession.setCallback(sessionCallback, mHandler);
+ mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mSession.setActive(true);
+
+ MediaController controller = mSession.getController();
+ setPlaybackState(PlaybackState.STATE_PLAYING);
+
+ sessionCallback.reset(1);
+ mSession.setCallback(null, mHandler);
+
+ controller.getTransportControls().pause();
+ assertFalse(sessionCallback.await(WAIT_MS));
+ assertFalse("Callback shouldn't be called.", sessionCallback.mOnPauseCalled);
+ }
+
private void setPlaybackState(int state) {
final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
| PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP
diff --git a/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
index 45b2b8b..d29cfed 100644
--- a/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
@@ -157,6 +157,7 @@
try {
testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
"unknown-property", value);
+ fail("Should have thrown an exception");
} catch (RuntimeException e) {
Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
assertThat(e.getMessage(), containsString("get property string returns"));
diff --git a/tests/tests/media/src/android/media/cts/ParamsTest.java b/tests/tests/media/src/android/media/cts/ParamsTest.java
index 6cf9be6..fab616a 100644
--- a/tests/tests/media/src/android/media/cts/ParamsTest.java
+++ b/tests/tests/media/src/android/media/cts/ParamsTest.java
@@ -373,59 +373,18 @@
}
public void testBufferingParamsBuilderAndGet() {
- final int initialMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
final int initialMarkMs = 2;
- final int initialMarkKB = 20;
- final int rebufferingMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
- final int rebufferingMarkLowMs = 1;
- final int rebufferingMarkHighMs = 3;
- final int rebufferingMarkLowKB = 10;
- final int rebufferingMarkHighKB = 30;
+ final int resumePlaybackMarkMs = 3;
BufferingParams p1 = new BufferingParams.Builder()
- .setInitialBufferingMode(initialMode)
- .setInitialBufferingWatermarkMs(initialMarkMs)
- .setInitialBufferingWatermarkKB(initialMarkKB)
- .setRebufferingMode(rebufferingMode)
- .setRebufferingWatermarkLowMs(rebufferingMarkLowMs)
- .setRebufferingWatermarkHighMs(rebufferingMarkHighMs)
- .setRebufferingWatermarkLowKB(rebufferingMarkLowKB)
- .setRebufferingWatermarkHighKB(rebufferingMarkHighKB)
+ .setInitialMarkMs(initialMarkMs)
+ .setResumePlaybackMarkMs(resumePlaybackMarkMs)
.build();
- assertEquals("initial buffering mode should match",
- p1.getInitialBufferingMode(), initialMode);
- assertEquals("rebuffering mode should match",
- p1.getRebufferingMode(), rebufferingMode);
assertEquals("intial markMs should match",
- p1.getInitialBufferingWatermarkMs(), initialMarkMs);
- assertEquals("intial markKB should match",
- p1.getInitialBufferingWatermarkKB(), initialMarkKB);
- assertEquals("rebuffering low markMs should match",
- p1.getRebufferingWatermarkLowMs(), rebufferingMarkLowMs);
- assertEquals("rebuffering low markKB should match",
- p1.getRebufferingWatermarkLowKB(), rebufferingMarkLowKB);
- assertEquals("rebuffering high markMs should match",
- p1.getRebufferingWatermarkHighMs(), rebufferingMarkHighMs);
- assertEquals("rebuffering high markKB should match",
- p1.getRebufferingWatermarkHighKB(), rebufferingMarkHighKB);
-
- final int rebufferingMarkLowMsPair = 4;
- final int rebufferingMarkHighMsPair = 5;
- final int rebufferingMarkLowKBPair = 40;
- final int rebufferingMarkHighKBPair = 50;
- BufferingParams p2 = new BufferingParams.Builder(p1)
- .setRebufferingWatermarksMs(rebufferingMarkLowMsPair, rebufferingMarkHighMsPair)
- .setRebufferingWatermarksKB(rebufferingMarkLowKBPair, rebufferingMarkHighKBPair)
- .build();
- assertEquals("paired low markMs should match",
- p2.getRebufferingWatermarkLowMs(), rebufferingMarkLowMsPair);
- assertEquals("paired low markKB should match",
- p2.getRebufferingWatermarkLowKB(), rebufferingMarkLowKBPair);
- assertEquals("paired high markMs should match",
- p2.getRebufferingWatermarkHighMs(), rebufferingMarkHighMsPair);
- assertEquals("paired high markKB should match",
- p2.getRebufferingWatermarkHighKB(), rebufferingMarkHighKBPair);
+ p1.getInitialMarkMs(), initialMarkMs);
+ assertEquals("resume playback markMs should match",
+ p1.getResumePlaybackMarkMs(), resumePlaybackMarkMs);
}
public void testBufferingParamsDescribeContents() {
@@ -434,24 +393,12 @@
}
public void testBufferingParamsWriteToParcel() {
- final int initialMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
final int initialMarkMs = 2;
- final int initialMarkKB = 20;
- final int rebufferingMode = BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE;
- final int rebufferingMarkLowMs = 1;
- final int rebufferingMarkHighMs = 3;
- final int rebufferingMarkLowKB = 10;
- final int rebufferingMarkHighKB = 30;
+ final int resumePlaybackMarkMs = 3;
BufferingParams p = new BufferingParams.Builder()
- .setInitialBufferingMode(initialMode)
- .setInitialBufferingWatermarkMs(initialMarkMs)
- .setInitialBufferingWatermarkKB(initialMarkKB)
- .setRebufferingMode(rebufferingMode)
- .setRebufferingWatermarkLowMs(rebufferingMarkLowMs)
- .setRebufferingWatermarkHighMs(rebufferingMarkHighMs)
- .setRebufferingWatermarkLowKB(rebufferingMarkLowKB)
- .setRebufferingWatermarkHighKB(rebufferingMarkHighKB)
+ .setInitialMarkMs(initialMarkMs)
+ .setResumePlaybackMarkMs(resumePlaybackMarkMs)
.build();
Parcel parcel = Parcel.obtain();
@@ -459,22 +406,10 @@
parcel.setDataPosition(0);
BufferingParams q = BufferingParams.CREATOR.createFromParcel(parcel);
- assertEquals("initial buffering mode should match",
- p.getInitialBufferingMode(), q.getInitialBufferingMode());
- assertEquals("rebuffering mode should match",
- p.getRebufferingMode(), q.getRebufferingMode());
- assertEquals("initial buffering markMs should match",
- p.getInitialBufferingWatermarkMs(), q.getInitialBufferingWatermarkMs());
- assertEquals("initial buffering markKB should match",
- p.getInitialBufferingWatermarkKB(), q.getInitialBufferingWatermarkKB());
- assertEquals("rebuffering low markMs should match",
- p.getRebufferingWatermarkLowMs(), q.getRebufferingWatermarkLowMs());
- assertEquals("rebuffering low markKB should match",
- p.getRebufferingWatermarkLowKB(), q.getRebufferingWatermarkLowKB());
- assertEquals("rebuffering high markMs should match",
- p.getRebufferingWatermarkHighMs(), q.getRebufferingWatermarkHighMs());
- assertEquals("rebuffering high markKB should match",
- p.getRebufferingWatermarkHighKB(), q.getRebufferingWatermarkHighKB());
+ assertEquals("initial markMs should match",
+ p.getInitialMarkMs(), q.getInitialMarkMs());
+ assertEquals("resume playback markMs should match",
+ p.getResumePlaybackMarkMs(), q.getResumePlaybackMarkMs());
parcel.recycle();
}
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
index 7614568..ddb534a 100644
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingTest.java
@@ -19,25 +19,39 @@
import android.content.Context;
import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioRouting;
import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.MediaFormat;
import android.media.MediaRecorder;
+import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.util.Log;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
import java.lang.Runnable;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
- * AudioTrack / AudioRecord preferred device and routing listener tests.
+ * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
+ * and routing listener tests.
* The routing tests are mostly here to exercise the routing code, as an actual test would require
* adding / removing an audio device for the listeners to be called.
* The routing listener code is designed to run for two versions of the routing code:
@@ -46,8 +60,18 @@
*/
public class RoutingTest extends AndroidTestCase {
private static final String TAG = "RoutingTest";
+ private static final int MAX_WAITING_ROUTING_CHANGED_COUNT = 3;
+ private static final long WAIT_ROUTING_CHANGE_TIME_MS = 1000;
+ private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
+ private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
+ private static final long MAX_FILE_SIZE_BYTE = 5000;
+ private static final int RECORD_TIME_MS = 3000;
+ private static final Set<Integer> AVAILABLE_INPUT_DEVICES_TYPE = new HashSet<>(
+ Arrays.asList(AudioDeviceInfo.TYPE_BUILTIN_MIC));
private AudioManager mAudioManager;
+ private CountDownLatch mRoutingChangedLatch;
+ private File mOutFile;
@Override
protected void setUp() throws Exception {
@@ -58,6 +82,14 @@
assertNotNull(mAudioManager);
}
+ @Override
+ protected void tearDown() throws Exception {
+ if (mOutFile != null && mOutFile.exists()) {
+ mOutFile.delete();
+ }
+ super.tearDown();
+ }
+
private AudioTrack allocAudioTrack() {
int bufferSize =
AudioTrack.getMinBufferSize(
@@ -455,4 +487,260 @@
audioRecord.stop();
audioRecord.release();
}
+
+ private class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
+ {
+ public void onRoutingChanged(AudioRouting audioRouting) {
+ if (mRoutingChangedLatch != null) {
+ mRoutingChangedLatch.countDown();
+ }
+ }
+ }
+
+ private MediaPlayer allocMediaPlayer() {
+ final int resid = R.raw.testmp3_2;
+ MediaPlayer mediaPlayer = MediaPlayer.create(mContext, resid);
+ mediaPlayer.setAudioAttributes(
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build());
+ mediaPlayer.start();
+ return mediaPlayer;
+ }
+
+ public void test_mediaPlayer_preferredDevice() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ // Can't do it so skip this test
+ return;
+ }
+
+ MediaPlayer mediaPlayer = allocMediaPlayer();
+ assertTrue(mediaPlayer.isPlaying());
+
+ // None selected (new MediaPlayer), so check for default
+ assertNull(mediaPlayer.getPreferredDevice());
+
+ // resets to default
+ assertTrue(mediaPlayer.setPreferredDevice(null));
+
+ // test each device
+ AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (int index = 0; index < deviceList.length; index++) {
+ assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
+ assertTrue(mediaPlayer.getPreferredDevice() == deviceList[index]);
+ }
+
+ // Check defaults again
+ assertTrue(mediaPlayer.setPreferredDevice(null));
+ assertNull(mediaPlayer.getPreferredDevice());
+
+ mediaPlayer.stop();
+ mediaPlayer.release();
+ }
+
+ public void test_mediaPlayer_getRoutedDevice() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ // Can't do it so skip this test
+ return;
+ }
+
+ MediaPlayer mediaPlayer = allocMediaPlayer();
+ assertTrue(mediaPlayer.isPlaying());
+
+ // Sleep for 1s to ensure the output device open
+ SystemClock.sleep(1000);
+
+ // No explicit route
+ AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
+ assertNotNull(routedDevice);
+
+ mediaPlayer.stop();
+ mediaPlayer.release();
+ }
+
+ public void test_MediaPlayer_RoutingListener() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ // Can't do it so skip this test
+ return;
+ }
+
+ MediaPlayer mediaPlayer = allocMediaPlayer();
+
+ // null listener
+ mediaPlayer.addOnRoutingChangedListener(null, null);
+
+ AudioRoutingListener listener = new AudioRoutingListener();
+ AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+ // add a listener
+ mediaPlayer.addOnRoutingChangedListener(listener, null);
+
+ // remove listeners
+ // remove a listener we didn't add
+ mediaPlayer.removeOnRoutingChangedListener(someOtherListener);
+ // remove a valid listener
+ mediaPlayer.removeOnRoutingChangedListener(listener);
+
+ Looper myLooper = prepareIfNeededLooper();
+
+ mediaPlayer.addOnRoutingChangedListener(listener, new Handler());
+ mediaPlayer.removeOnRoutingChangedListener(listener);
+
+ mediaPlayer.stop();
+ mediaPlayer.release();
+ if (myLooper != null) {
+ myLooper.quit();
+ }
+ }
+
+ public void test_MediaPlayer_RoutingChangedCallback() {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+ // Can't do it so skip this test
+ return;
+ }
+
+ MediaPlayer mediaPlayer = allocMediaPlayer();
+ AudioRoutingListener listener = new AudioRoutingListener();
+ mediaPlayer.addOnRoutingChangedListener(listener, null);
+
+ AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ if (deviceList.length < 2) {
+ // The available output device is less than 2, we can't switch output device.
+ return;
+ }
+ for (int index = 0; index < deviceList.length; index++) {
+ assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
+ boolean routingChanged = false;
+ for (int i = 0; i < MAX_WAITING_ROUTING_CHANGED_COUNT; i++) {
+ // Create a new CountDownLatch in case it is triggered by previous routing change.
+ mRoutingChangedLatch = new CountDownLatch(1);
+ try {
+ mRoutingChangedLatch.await(WAIT_ROUTING_CHANGE_TIME_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ }
+ AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
+ if (routedDevice == null) {
+ continue;
+ }
+ if (routedDevice.getId() == deviceList[index].getId()) {
+ routingChanged = true;
+ break;
+ }
+ }
+ assertTrue("Switching to device" + deviceList[index].getType() + " failed",
+ routingChanged);
+ }
+
+ mediaPlayer.removeOnRoutingChangedListener(listener);
+ mediaPlayer.stop();
+ mediaPlayer.release();
+ }
+
+ private MediaRecorder allocMediaRecorder() throws Exception {
+ final String outputPath = new File(Environment.getExternalStorageDirectory(),
+ "record.out").getAbsolutePath();
+ mOutFile = new File(outputPath);
+ MediaRecorder mediaRecorder = new MediaRecorder();
+ mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ assertEquals(0, mediaRecorder.getMaxAmplitude());
+ mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mediaRecorder.setOutputFile(outputPath);
+ mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mediaRecorder.setAudioChannels(AudioFormat.CHANNEL_OUT_DEFAULT);
+ mediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
+ mediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
+ mediaRecorder.setMaxFileSize(MAX_FILE_SIZE_BYTE);
+ mediaRecorder.prepare();
+ mediaRecorder.start();
+ // Sleep a while to ensure the underlying AudioRecord is initialized.
+ Thread.sleep(1000);
+ return mediaRecorder;
+ }
+
+ public void test_mediaRecorder_preferredDevice() throws Exception {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+ || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ MediaUtils.skipTest("no audio codecs or microphone");
+ return;
+ }
+
+ MediaRecorder mediaRecorder = allocMediaRecorder();
+
+ // None selected (new MediaPlayer), so check for default
+ assertNull(mediaRecorder.getPreferredDevice());
+
+ // resets to default
+ assertTrue(mediaRecorder.setPreferredDevice(null));
+
+ // test each device
+ AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ for (int index = 0; index < deviceList.length; index++) {
+ if (!AVAILABLE_INPUT_DEVICES_TYPE.contains(deviceList[index].getType())) {
+ // Only try to set devices whose type is contained in predefined set as preferred
+ // device in case of permission denied when switching input device.
+ continue;
+ }
+ assertTrue(mediaRecorder.setPreferredDevice(deviceList[index]));
+ assertTrue(mediaRecorder.getPreferredDevice() == deviceList[index]);
+ }
+
+ // Check defaults again
+ assertTrue(mediaRecorder.setPreferredDevice(null));
+ assertNull(mediaRecorder.getPreferredDevice());
+ Thread.sleep(RECORD_TIME_MS);
+
+ mediaRecorder.stop();
+ mediaRecorder.release();
+ }
+
+ public void test_mediaRecorder_getRoutedDeviceId() throws Exception {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+ || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ MediaUtils.skipTest("no audio codecs or microphone");
+ return;
+ }
+
+ MediaRecorder mediaRecorder = allocMediaRecorder();
+
+ AudioDeviceInfo routedDevice = mediaRecorder.getRoutedDevice();
+ assertNotNull(routedDevice); // we probably can't say anything more than this
+ Thread.sleep(RECORD_TIME_MS);
+
+ mediaRecorder.stop();
+ mediaRecorder.release();
+ }
+
+ public void test_mediaRecorder_RoutingListener() throws Exception {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+ || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ MediaUtils.skipTest("no audio codecs or microphone");
+ return;
+ }
+
+ MediaRecorder mediaRecorder = allocMediaRecorder();
+
+ // null listener
+ mediaRecorder.addOnRoutingChangedListener(null, null);
+
+ AudioRoutingListener listener = new AudioRoutingListener();
+ AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+ // add a listener
+ mediaRecorder.addOnRoutingChangedListener(listener, null);
+
+ // remove listeners we didn't add
+ mediaRecorder.removeOnRoutingChangedListener(someOtherListener);
+ // remove a valid listener
+ mediaRecorder.removeOnRoutingChangedListener(listener);
+
+ Looper myLooper = prepareIfNeededLooper();
+ mediaRecorder.addOnRoutingChangedListener(listener, new Handler());
+ mediaRecorder.removeOnRoutingChangedListener(listener);
+
+ Thread.sleep(RECORD_TIME_MS);
+
+ mediaRecorder.stop();
+ mediaRecorder.release();
+ if (myLooper != null) {
+ myLooper.quit();
+ }
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index ab54e75..3a0d512 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -391,10 +391,22 @@
return; // skip
}
- // getDefaultBufferingParams should be called after setDataSource.
+ // getBufferingParams should be called after setDataSource.
try {
- BufferingParams params = mMediaPlayer.getDefaultBufferingParams();
- fail("MediaPlayer failed to check state for getDefaultBufferingParams");
+ BufferingParams params = mMediaPlayer.getBufferingParams();
+ fail("MediaPlayer failed to check state for getBufferingParams");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+
+ // setBufferingParams should be called after setDataSource.
+ try {
+ BufferingParams params = new BufferingParams.Builder()
+ .setInitialMarkMs(2)
+ .setResumePlaybackMarkMs(3)
+ .build();
+ mMediaPlayer.setBufferingParams(params);
+ fail("MediaPlayer failed to check state for setBufferingParams");
} catch (IllegalStateException e) {
// expected
}
@@ -421,33 +433,17 @@
assertFalse(mOnBufferingUpdateCalled.isSignalled());
- BufferingParams params = mMediaPlayer.getDefaultBufferingParams();
+ BufferingParams params = mMediaPlayer.getBufferingParams();
- int newMark = -1;
- BufferingParams newParams = null;
- int initialBufferingMode = params.getInitialBufferingMode();
- if (initialBufferingMode == BufferingParams.BUFFERING_MODE_SIZE_ONLY
- || initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE) {
- newMark = params.getInitialBufferingWatermarkKB() + 1;
- newParams = new BufferingParams.Builder(params).setInitialBufferingWatermarkKB(
- newMark).build();
- } else if (initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_ONLY) {
- newMark = params.getInitialBufferingWatermarkMs() + 1;
- newParams = new BufferingParams.Builder(params).setInitialBufferingWatermarkMs(
- newMark).build();
- } else {
- newParams = params;
- }
+ int newMark = params.getInitialMarkMs() + 2;
+ BufferingParams newParams =
+ new BufferingParams.Builder(params).setInitialMarkMs(newMark).build();
+
mMediaPlayer.setBufferingParams(newParams);
int checkMark = -1;
BufferingParams checkParams = mMediaPlayer.getBufferingParams();
- if (initialBufferingMode == BufferingParams.BUFFERING_MODE_SIZE_ONLY
- || initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_THEN_SIZE) {
- checkMark = checkParams.getInitialBufferingWatermarkKB();
- } else if (initialBufferingMode == BufferingParams.BUFFERING_MODE_TIME_ONLY) {
- checkMark = checkParams.getInitialBufferingWatermarkMs();
- }
+ checkMark = checkParams.getInitialMarkMs();
assertEquals("marks do not match", newMark, checkMark);
// TODO: add more dynamic checking, e.g., buffering shall not exceed pre-set mark.
diff --git a/tests/tests/mediastress/Android.mk b/tests/tests/mediastress/Android.mk
index 0213c00..633708f 100644
--- a/tests/tests/mediastress/Android.mk
+++ b/tests/tests/mediastress/Android.mk
@@ -28,6 +28,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_HOST_SHARED_LIBRARIES := compatibility-device-media-preconditions
LOCAL_JNI_SHARED_LIBRARIES := libctsmediastress_jni libnativehelper_compat_libc++
diff --git a/tests/tests/mediastress/AndroidTest.xml b/tests/tests/mediastress/AndroidTest.xml
index 3572ab8..5d186b8 100644
--- a/tests/tests/mediastress/AndroidTest.xml
+++ b/tests/tests/mediastress/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Media Stress test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/midi/Android.mk b/tests/tests/midi/Android.mk
index cefe3cc..212c8ac 100755
--- a/tests/tests/midi/Android.mk
+++ b/tests/tests/midi/Android.mk
@@ -27,6 +27,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
# Must match the package name in CtsTestCaseList.mk
diff --git a/tests/tests/midi/AndroidTest.xml b/tests/tests/midi/AndroidTest.xml
index f0b3cce..d8d12e2 100644
--- a/tests/tests/midi/AndroidTest.xml
+++ b/tests/tests/midi/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS MIDI test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/multiuser/Android.mk b/tests/tests/multiuser/Android.mk
index 992b5fe..7c9ca7e 100644
--- a/tests/tests/multiuser/Android.mk
+++ b/tests/tests/multiuser/Android.mk
@@ -29,6 +29,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
LOCAL_SDK_VERSION := test_current
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index 823ec5f..2edb2ec 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -15,6 +15,7 @@
~ limitations under the License
-->
<configuration description="Config for CTS Multiuser test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/nativehardware/Android.mk b/tests/tests/nativehardware/Android.mk
index 38688be..21f2d9a 100644
--- a/tests/tests/nativehardware/Android.mk
+++ b/tests/tests/nativehardware/Android.mk
@@ -27,7 +27,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util
-LOCAL_JAVA_LIBRARIES := platform-test-annotations
+LOCAL_JAVA_LIBRARIES := platform-test-annotations android.test.base.stubs
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/nativehardware/AndroidTest.xml b/tests/tests/nativehardware/AndroidTest.xml
index 074a962..43457b3 100644
--- a/tests/tests/nativehardware/AndroidTest.xml
+++ b/tests/tests/nativehardware/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Native Hardware test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="vr" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/nativemedia/aaudio/Android.mk b/tests/tests/nativemedia/aaudio/Android.mk
index 2f64131..2017eba 100644
--- a/tests/tests/nativemedia/aaudio/Android.mk
+++ b/tests/tests/nativemedia/aaudio/Android.mk
@@ -1,4 +1,4 @@
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,40 +12,30 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Build the unit tests.
-
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE := CtsNativeMediaAAudioTestCases
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
+
+LOCAL_PACKAGE_NAME := CtsNativeMediaAAudioTestCases
+
+# Include both the 32 and 64 bit versions
LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-LOCAL_SRC_FILES := \
- src/test_aaudio.cpp \
- src/test_aaudio_callback.cpp \
- src/test_aaudio_misc.cpp \
- src/test_aaudio_mmap.cpp \
- src/test_aaudio_stream_builder.cpp \
- src/utils.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libaaudio \
- liblog \
-
-LOCAL_STATIC_LIBRARIES := \
- libgtest_ndk_c++ \
-
-LOCAL_CTS_TEST_PACKAGE := android.nativemedia.aaudio
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CFLAGS := -Werror -Wall
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner nativetesthelper
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativeaaudiotest
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-include $(BUILD_CTS_EXECUTABLE)
+include $(BUILD_CTS_PACKAGE)
+
+# Include the associated library's makefile.
+include $(LOCAL_PATH)/jni/Android.mk
diff --git a/tests/tests/nativemedia/aaudio/AndroidManifest.xml b/tests/tests/nativemedia/aaudio/AndroidManifest.xml
new file mode 100644
index 0000000..97bf5f9
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nativemedia.aaudio">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- This is a self-instrumenting test package. -->
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.nativemedia.aaudio"
+ android:label="CTS tests of native AAudio API">
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/nativemedia/aaudio/AndroidTest.xml b/tests/tests/nativemedia/aaudio/AndroidTest.xml
index 7030d0c..b796ea6 100644
--- a/tests/tests/nativemedia/aaudio/AndroidTest.xml
+++ b/tests/tests/nativemedia/aaudio/AndroidTest.xml
@@ -15,16 +15,14 @@
-->
<configuration description="Config for CTS Native Media AAudio test cases">
<option name="config-descriptor:metadata" key="component" value="media" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
- <option name="cleanup" value="true" />
- <option name="push" value="CtsNativeMediaAAudioTestCases->/data/local/tmp/CtsNativeMediaAAudioTestCases" />
- <option name="append-bitness" value="true" />
+ <option name="test-suite-tag" value="cts" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNativeMediaAAudioTestCases.apk" />
</target_preparer>
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="CtsNativeMediaAAudioTestCases" />
- <option name="runtime-hint" value="2m" />
- <!-- test-timeout unit is ms, value = 2 min -->
- <option name="native-test-timeout" value="120000" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.nativemedia.aaudio" />
+ <option name="runtime-hint" value="2m0s" />
</test>
</configuration>
diff --git a/tests/tests/nativemedia/aaudio/jni/Android.mk b/tests/tests/nativemedia/aaudio/jni/Android.mk
new file mode 100644
index 0000000..c6f0902
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/Android.mk
@@ -0,0 +1,42 @@
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Build the unit tests.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libnativeaaudiotest
+LOCAL_MULTILIB := both
+
+LOCAL_SRC_FILES := \
+ test_aaudio.cpp \
+ test_aaudio_callback.cpp \
+ test_aaudio_mmap.cpp \
+ test_aaudio_misc.cpp \
+ test_aaudio_stream_builder.cpp \
+ utils.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libaaudio \
+ liblog
+
+LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
+
+LOCAL_CFLAGS := -Werror -Wall
+
+LOCAL_SDK_VERSION := current
+LOCAL_NDK_STL_VARIANT := c++_static
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio.h b/tests/tests/nativemedia/aaudio/jni/test_aaudio.h
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio.h
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio.h
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_callback.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_misc.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_misc.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_misc.cpp
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_mmap.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_mmap.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_mmap.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_mmap.cpp
diff --git a/tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
similarity index 100%
rename from tests/tests/nativemedia/aaudio/src/test_aaudio_stream_builder.cpp
rename to tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.cpp b/tests/tests/nativemedia/aaudio/jni/utils.cpp
new file mode 100644
index 0000000..55d1e13
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/utils.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AAudioTest"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android/log.h>
+#include <gtest/gtest.h>
+
+#include "test_aaudio.h"
+#include "utils.h"
+
+int64_t getNanoseconds(clockid_t clockId) {
+ struct timespec time;
+ int result = clock_gettime(clockId, &time);
+ if (result < 0) {
+ return -errno;
+ }
+ return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
+}
+
+const char* performanceModeToString(aaudio_performance_mode_t mode) {
+ switch (mode) {
+ case AAUDIO_PERFORMANCE_MODE_NONE: return "DEFAULT";
+ case AAUDIO_PERFORMANCE_MODE_POWER_SAVING: return "POWER_SAVING";
+ case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: return "LOW_LATENCY";
+ }
+ return "UNKNOWN";
+}
+
+const char* sharingModeToString(aaudio_sharing_mode_t mode) {
+ switch (mode) {
+ case AAUDIO_SHARING_MODE_SHARED: return "SHARED";
+ case AAUDIO_SHARING_MODE_EXCLUSIVE: return "EXCLUSIVE";
+ }
+ return "UNKNOWN";
+}
+
+
+// These periods are quite generous. They are not designed to put
+// any restrictions on the implementation, but only to ensure sanity.
+// Use int64_t because 96000 * 30000 is close to int32_t limits.
+const std::map<aaudio_performance_mode_t, int64_t> StreamBuilderHelper::sMaxFramesPerBurstMs =
+{ { AAUDIO_PERFORMANCE_MODE_NONE, 128 },
+ { AAUDIO_PERFORMANCE_MODE_POWER_SAVING, 30 * 1000 },
+ { AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, 40 } };
+
+StreamBuilderHelper::StreamBuilderHelper(
+ aaudio_direction_t direction, int32_t sampleRate,
+ int32_t channelCount, aaudio_format_t dataFormat,
+ aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode)
+ : mDirection{direction},
+ mRequested{sampleRate, channelCount, dataFormat, sharingMode, perfMode},
+ mActual{0, 0, AAUDIO_FORMAT_INVALID, -1, -1}, mFramesPerBurst{-1},
+ mBuilder{nullptr}, mStream{nullptr} {}
+
+StreamBuilderHelper::~StreamBuilderHelper() {
+ close();
+}
+
+void StreamBuilderHelper::initBuilder() {
+ ASSERT_EQ(1U, sMaxFramesPerBurstMs.count(mRequested.perfMode));
+
+ // Use an AAudioStreamBuilder to define the stream.
+ aaudio_result_t result = AAudio_createStreamBuilder(&mBuilder);
+ ASSERT_EQ(AAUDIO_OK, result);
+ ASSERT_TRUE(mBuilder != nullptr);
+
+ // Request stream properties.
+ AAudioStreamBuilder_setDeviceId(mBuilder, AAUDIO_UNSPECIFIED);
+ AAudioStreamBuilder_setDirection(mBuilder, mDirection);
+ AAudioStreamBuilder_setSampleRate(mBuilder, mRequested.sampleRate);
+ AAudioStreamBuilder_setChannelCount(mBuilder, mRequested.channelCount);
+ AAudioStreamBuilder_setFormat(mBuilder, mRequested.dataFormat);
+ AAudioStreamBuilder_setSharingMode(mBuilder, mRequested.sharingMode);
+ AAudioStreamBuilder_setPerformanceMode(mBuilder, mRequested.perfMode);
+}
+
+// Needs to be a 'void' function due to ASSERT requirements.
+void StreamBuilderHelper::createAndVerifyStream(bool *success) {
+ *success = false;
+
+ aaudio_result_t result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
+ if (mRequested.sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE && result != AAUDIO_OK) {
+ __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "Could not open a stream in EXCLUSIVE mode");
+ return;
+ }
+ ASSERT_EQ(AAUDIO_OK, result);
+ ASSERT_TRUE(mStream != nullptr);
+ ASSERT_EQ(AAUDIO_STREAM_STATE_OPEN, AAudioStream_getState(mStream));
+ ASSERT_EQ(mDirection, AAudioStream_getDirection(mStream));
+
+ mActual.sharingMode = AAudioStream_getSharingMode(mStream);
+ if (mActual.sharingMode != mRequested.sharingMode) {
+ // Since we are covering all possible values, the "actual" mode
+ // will also be tested, so no need to run the same test twice.
+ __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Sharing mode %s is not available",
+ sharingModeToString(mRequested.sharingMode));
+ return;
+ }
+
+ // Check to see what kind of stream we actually got.
+ mActual.sampleRate = AAudioStream_getSampleRate(mStream);
+ ASSERT_GE(mActual.sampleRate, 44100);
+ ASSERT_LE(mActual.sampleRate, 96000); // TODO what is min/max?
+
+ mActual.channelCount = AAudioStream_getChannelCount(mStream);
+ ASSERT_GE(mActual.channelCount, 1);
+ ASSERT_LE(mActual.channelCount, 16); // TODO what is min/max?
+
+ mActual.dataFormat = AAudioStream_getFormat(mStream);
+ ASSERT_EQ(AAUDIO_FORMAT_PCM_I16, mActual.dataFormat);
+
+ mActual.perfMode = AAudioStream_getPerformanceMode(mStream);
+ if (mRequested.perfMode != AAUDIO_PERFORMANCE_MODE_NONE
+ && mRequested.perfMode != mActual.perfMode) {
+ // Since we are covering all possible values, the "actual" mode
+ // will also be tested, so no need to run the same test twice.
+ __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Performance mode %s is not available",
+ performanceModeToString(mRequested.sharingMode));
+ return;
+ }
+
+ mFramesPerBurst = AAudioStream_getFramesPerBurst(mStream);
+ ASSERT_GE(mFramesPerBurst, 16);
+ const int32_t maxFramesPerBurst =
+ mActual.sampleRate * sMaxFramesPerBurstMs.at(mActual.perfMode) / MILLIS_PER_SECOND;
+ ASSERT_LE(mFramesPerBurst, maxFramesPerBurst);
+
+ int32_t actualBufferSize = AAudioStream_getBufferSizeInFrames(mStream);
+ ASSERT_GT(actualBufferSize, 0);
+ ASSERT_GT(AAudioStream_setBufferSizeInFrames(mStream, actualBufferSize), 0);
+
+ *success = true;
+}
+
+void StreamBuilderHelper::close() {
+ if (mBuilder != nullptr) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(mBuilder));
+ }
+ if (mStream != nullptr) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_close(mStream));
+ }
+}
+
+void StreamBuilderHelper::streamCommand(
+ StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState) {
+ ASSERT_EQ(AAUDIO_OK, cmd(mStream));
+ aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ ASSERT_EQ(AAUDIO_OK,
+ AAudioStream_waitForStateChange(mStream, fromState, &state, DEFAULT_STATE_TIMEOUT));
+ ASSERT_EQ(toState, state);
+}
+
+
+InputStreamBuilderHelper::InputStreamBuilderHelper(
+ aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
+ : StreamBuilderHelper{AAUDIO_DIRECTION_INPUT,
+ 48000, 1, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
+
+
+OutputStreamBuilderHelper::OutputStreamBuilderHelper(
+ aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
+ : StreamBuilderHelper{AAUDIO_DIRECTION_OUTPUT,
+ 48000, 2, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
+
+void OutputStreamBuilderHelper::initBuilder() {
+ StreamBuilderHelper::initBuilder();
+ AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, kBufferCapacityFrames);
+}
+
+void OutputStreamBuilderHelper::createAndVerifyStream(bool *success) {
+ StreamBuilderHelper::createAndVerifyStream(success);
+ if (*success) {
+ ASSERT_GE(AAudioStream_getBufferCapacityInFrames(mStream), kBufferCapacityFrames);
+ }
+}
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.h b/tests/tests/nativemedia/aaudio/jni/utils.h
new file mode 100644
index 0000000..7f38fef
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/utils.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CTS_MEDIA_TEST_AAUDIO_UTILS_H
+#define CTS_MEDIA_TEST_AAUDIO_UTILS_H
+
+#include <map>
+
+#include <aaudio/AAudio.h>
+
+int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC);
+const char* performanceModeToString(aaudio_performance_mode_t mode);
+const char* sharingModeToString(aaudio_sharing_mode_t mode);
+
+class StreamBuilderHelper {
+ public:
+ struct Parameters {
+ int32_t sampleRate;
+ int32_t channelCount;
+ aaudio_format_t dataFormat;
+ aaudio_sharing_mode_t sharingMode;
+ aaudio_performance_mode_t perfMode;
+ };
+
+ void initBuilder();
+ void createAndVerifyStream(bool *success);
+ void close();
+
+ void startStream() {
+ streamCommand(&AAudioStream_requestStart,
+ AAUDIO_STREAM_STATE_STARTING, AAUDIO_STREAM_STATE_STARTED);
+ }
+ void pauseStream() {
+ streamCommand(&AAudioStream_requestPause,
+ AAUDIO_STREAM_STATE_PAUSING, AAUDIO_STREAM_STATE_PAUSED);
+ }
+ void stopStream() {
+ streamCommand(&AAudioStream_requestStop,
+ AAUDIO_STREAM_STATE_STOPPING, AAUDIO_STREAM_STATE_STOPPED);
+ }
+ void flushStream() {
+ streamCommand(&AAudioStream_requestFlush,
+ AAUDIO_STREAM_STATE_FLUSHING, AAUDIO_STREAM_STATE_FLUSHED);
+ }
+
+ AAudioStreamBuilder* builder() const { return mBuilder; }
+ AAudioStream* stream() const { return mStream; }
+ const Parameters& actual() const { return mActual; }
+ int32_t framesPerBurst() const { return mFramesPerBurst; }
+
+ protected:
+ StreamBuilderHelper(aaudio_direction_t direction, int32_t sampleRate,
+ int32_t channelCount, aaudio_format_t dataFormat,
+ aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode);
+ ~StreamBuilderHelper();
+
+ typedef aaudio_result_t (StreamCommand)(AAudioStream*);
+ void streamCommand(
+ StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState);
+
+ static const std::map<aaudio_performance_mode_t, int64_t> sMaxFramesPerBurstMs;
+ const aaudio_direction_t mDirection;
+ const Parameters mRequested;
+ Parameters mActual;
+ int32_t mFramesPerBurst;
+ AAudioStreamBuilder *mBuilder;
+ AAudioStream *mStream;
+};
+
+class InputStreamBuilderHelper : public StreamBuilderHelper {
+ public:
+ InputStreamBuilderHelper(
+ aaudio_sharing_mode_t requestedSharingMode,
+ aaudio_performance_mode_t requestedPerfMode);
+};
+
+class OutputStreamBuilderHelper : public StreamBuilderHelper {
+ public:
+ OutputStreamBuilderHelper(
+ aaudio_sharing_mode_t requestedSharingMode,
+ aaudio_performance_mode_t requestedPerfMode);
+ void initBuilder();
+ void createAndVerifyStream(bool *success);
+
+ private:
+ const int32_t kBufferCapacityFrames = 2000;
+};
+
+#endif // CTS_MEDIA_TEST_AAUDIO_UTILS_H
diff --git a/tests/tests/nativemedia/aaudio/src/android/nativemedia/aaudio/AAudioTests.java b/tests/tests/nativemedia/aaudio/src/android/nativemedia/aaudio/AAudioTests.java
new file mode 100644
index 0000000..afb2db4
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/src/android/nativemedia/aaudio/AAudioTests.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nativemedia.aaudio;
+
+import org.junit.runner.RunWith;
+import com.android.gtestrunner.GtestRunner;
+import com.android.gtestrunner.TargetLibrary;
+
+@RunWith(GtestRunner.class)
+@TargetLibrary("nativeaaudiotest")
+public class AAudioTests {}
diff --git a/tests/tests/nativemedia/aaudio/src/utils.cpp b/tests/tests/nativemedia/aaudio/src/utils.cpp
deleted file mode 100644
index b039f4e..0000000
--- a/tests/tests/nativemedia/aaudio/src/utils.cpp
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AAudioTest"
-
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <android/log.h>
-#include <gtest/gtest.h>
-
-#include "test_aaudio.h"
-#include "utils.h"
-
-int64_t getNanoseconds(clockid_t clockId) {
- struct timespec time;
- int result = clock_gettime(clockId, &time);
- if (result < 0) {
- return -errno;
- }
- return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
-}
-
-const char* performanceModeToString(aaudio_performance_mode_t mode) {
- switch (mode) {
- case AAUDIO_PERFORMANCE_MODE_NONE: return "DEFAULT";
- case AAUDIO_PERFORMANCE_MODE_POWER_SAVING: return "POWER_SAVING";
- case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: return "LOW_LATENCY";
- }
- return "UNKNOWN";
-}
-
-const char* sharingModeToString(aaudio_sharing_mode_t mode) {
- switch (mode) {
- case AAUDIO_SHARING_MODE_SHARED: return "SHARED";
- case AAUDIO_SHARING_MODE_EXCLUSIVE: return "EXCLUSIVE";
- }
- return "UNKNOWN";
-}
-
-
-// These periods are quite generous. They are not designed to put
-// any restrictions on the implementation, but only to ensure sanity.
-// Use int64_t because 96000 * 30000 is close to int32_t limits.
-const std::map<aaudio_performance_mode_t, int64_t> StreamBuilderHelper::sMaxFramesPerBurstMs =
-{ { AAUDIO_PERFORMANCE_MODE_NONE, 128 },
- { AAUDIO_PERFORMANCE_MODE_POWER_SAVING, 30 * 1000 },
- { AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, 40 } };
-
-StreamBuilderHelper::StreamBuilderHelper(
- aaudio_direction_t direction, int32_t sampleRate,
- int32_t channelCount, aaudio_format_t dataFormat,
- aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode)
- : mDirection{direction},
- mRequested{sampleRate, channelCount, dataFormat, sharingMode, perfMode},
- mActual{0, 0, AAUDIO_FORMAT_INVALID, -1, -1}, mFramesPerBurst{-1},
- mBuilder{nullptr}, mStream{nullptr} {}
-
-StreamBuilderHelper::~StreamBuilderHelper() {
- close();
-}
-
-void StreamBuilderHelper::initBuilder() {
- ASSERT_EQ(1U, sMaxFramesPerBurstMs.count(mRequested.perfMode));
-
- // Use an AAudioStreamBuilder to define the stream.
- aaudio_result_t result = AAudio_createStreamBuilder(&mBuilder);
- ASSERT_EQ(AAUDIO_OK, result);
- ASSERT_TRUE(mBuilder != nullptr);
-
- // Request stream properties.
- AAudioStreamBuilder_setDeviceId(mBuilder, AAUDIO_UNSPECIFIED);
- AAudioStreamBuilder_setDirection(mBuilder, mDirection);
- AAudioStreamBuilder_setSampleRate(mBuilder, mRequested.sampleRate);
- AAudioStreamBuilder_setChannelCount(mBuilder, mRequested.channelCount);
- AAudioStreamBuilder_setFormat(mBuilder, mRequested.dataFormat);
- AAudioStreamBuilder_setSharingMode(mBuilder, mRequested.sharingMode);
- AAudioStreamBuilder_setPerformanceMode(mBuilder, mRequested.perfMode);
-}
-
-// Needs to be a 'void' function due to ASSERT requirements.
-void StreamBuilderHelper::createAndVerifyStream(bool *success) {
- *success = false;
-
- aaudio_result_t result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
- if (mRequested.sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE && result != AAUDIO_OK) {
- __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "Could not open a stream in EXCLUSIVE mode");
- return;
- }
- ASSERT_EQ(AAUDIO_OK, result);
- ASSERT_TRUE(mStream != nullptr);
- ASSERT_EQ(AAUDIO_STREAM_STATE_OPEN, AAudioStream_getState(mStream));
- ASSERT_EQ(mDirection, AAudioStream_getDirection(mStream));
-
- mActual.sharingMode = AAudioStream_getSharingMode(mStream);
- if (mActual.sharingMode != mRequested.sharingMode) {
- // Since we are covering all possible values, the "actual" mode
- // will also be tested, so no need to run the same test twice.
- __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Sharing mode %s is not available",
- sharingModeToString(mRequested.sharingMode));
- return;
- }
-
- // Check to see what kind of stream we actually got.
- mActual.sampleRate = AAudioStream_getSampleRate(mStream);
- ASSERT_GE(mActual.sampleRate, 44100);
- ASSERT_LE(mActual.sampleRate, 96000); // TODO what is min/max?
-
- mActual.channelCount = AAudioStream_getChannelCount(mStream);
- ASSERT_GE(mActual.channelCount, 1);
- ASSERT_LE(mActual.channelCount, 16); // TODO what is min/max?
-
- mActual.dataFormat = AAudioStream_getFormat(mStream);
- ASSERT_EQ(AAUDIO_FORMAT_PCM_I16, mActual.dataFormat);
-
- mActual.perfMode = AAudioStream_getPerformanceMode(mStream);
- if (mRequested.perfMode != AAUDIO_PERFORMANCE_MODE_NONE
- && mRequested.perfMode != mActual.perfMode) {
- // Since we are covering all possible values, the "actual" mode
- // will also be tested, so no need to run the same test twice.
- __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Performance mode %s is not available",
- performanceModeToString(mRequested.sharingMode));
- return;
- }
-
- mFramesPerBurst = AAudioStream_getFramesPerBurst(mStream);
- ASSERT_GE(mFramesPerBurst, 16);
- const int32_t maxFramesPerBurst =
- mActual.sampleRate * sMaxFramesPerBurstMs.at(mActual.perfMode) / MILLIS_PER_SECOND;
- ASSERT_LE(mFramesPerBurst, maxFramesPerBurst);
-
- int32_t actualBufferSize = AAudioStream_getBufferSizeInFrames(mStream);
- ASSERT_GT(actualBufferSize, 0);
- ASSERT_GT(AAudioStream_setBufferSizeInFrames(mStream, actualBufferSize), 0);
-
- *success = true;
-}
-
-void StreamBuilderHelper::close() {
- if (mBuilder != nullptr) {
- ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(mBuilder));
- }
- if (mStream != nullptr) {
- ASSERT_EQ(AAUDIO_OK, AAudioStream_close(mStream));
- }
-}
-
-void StreamBuilderHelper::streamCommand(
- StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState) {
- ASSERT_EQ(AAUDIO_OK, cmd(mStream));
- aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
- ASSERT_EQ(AAUDIO_OK,
- AAudioStream_waitForStateChange(mStream, fromState, &state, DEFAULT_STATE_TIMEOUT));
- ASSERT_EQ(toState, state);
-}
-
-
-InputStreamBuilderHelper::InputStreamBuilderHelper(
- aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
- : StreamBuilderHelper{AAUDIO_DIRECTION_INPUT,
- 48000, 1, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
-
-// Native apps don't have permissions, thus recording can
-// only be tested when running as root.
-static bool canTestRecording() {
- static const bool runningAsRoot = getuid() == 0;
- return runningAsRoot;
-}
-
-void InputStreamBuilderHelper::createAndVerifyStream(bool *success) {
- if (!canTestRecording()) {
- __android_log_write(ANDROID_LOG_WARN, LOG_TAG, "No permissions to run recording tests");
- *success = false;
- } else {
- StreamBuilderHelper::createAndVerifyStream(success);
- }
-}
-
-
-OutputStreamBuilderHelper::OutputStreamBuilderHelper(
- aaudio_sharing_mode_t requestedSharingMode, aaudio_performance_mode_t requestedPerfMode)
- : StreamBuilderHelper{AAUDIO_DIRECTION_OUTPUT,
- 48000, 2, AAUDIO_FORMAT_PCM_I16, requestedSharingMode, requestedPerfMode} {}
-
-void OutputStreamBuilderHelper::initBuilder() {
- StreamBuilderHelper::initBuilder();
- AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, kBufferCapacityFrames);
-}
-
-void OutputStreamBuilderHelper::createAndVerifyStream(bool *success) {
- StreamBuilderHelper::createAndVerifyStream(success);
- if (*success) {
- ASSERT_GE(AAudioStream_getBufferCapacityInFrames(mStream), kBufferCapacityFrames);
- }
-}
diff --git a/tests/tests/nativemedia/aaudio/src/utils.h b/tests/tests/nativemedia/aaudio/src/utils.h
deleted file mode 100644
index 44c3c69..0000000
--- a/tests/tests/nativemedia/aaudio/src/utils.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#ifndef CTS_MEDIA_TEST_AAUDIO_UTILS_H
-#define CTS_MEDIA_TEST_AAUDIO_UTILS_H
-
-#include <map>
-
-#include <aaudio/AAudio.h>
-
-int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC);
-const char* performanceModeToString(aaudio_performance_mode_t mode);
-const char* sharingModeToString(aaudio_sharing_mode_t mode);
-
-class StreamBuilderHelper {
- public:
- struct Parameters {
- int32_t sampleRate;
- int32_t channelCount;
- aaudio_format_t dataFormat;
- aaudio_sharing_mode_t sharingMode;
- aaudio_performance_mode_t perfMode;
- };
-
- void initBuilder();
- void createAndVerifyStream(bool *success);
- void close();
-
- void startStream() {
- streamCommand(&AAudioStream_requestStart,
- AAUDIO_STREAM_STATE_STARTING, AAUDIO_STREAM_STATE_STARTED);
- }
- void pauseStream() {
- streamCommand(&AAudioStream_requestPause,
- AAUDIO_STREAM_STATE_PAUSING, AAUDIO_STREAM_STATE_PAUSED);
- }
- void stopStream() {
- streamCommand(&AAudioStream_requestStop,
- AAUDIO_STREAM_STATE_STOPPING, AAUDIO_STREAM_STATE_STOPPED);
- }
- void flushStream() {
- streamCommand(&AAudioStream_requestFlush,
- AAUDIO_STREAM_STATE_FLUSHING, AAUDIO_STREAM_STATE_FLUSHED);
- }
-
- AAudioStreamBuilder* builder() const { return mBuilder; }
- AAudioStream* stream() const { return mStream; }
- const Parameters& actual() const { return mActual; }
- int32_t framesPerBurst() const { return mFramesPerBurst; }
-
- protected:
- StreamBuilderHelper(aaudio_direction_t direction, int32_t sampleRate,
- int32_t channelCount, aaudio_format_t dataFormat,
- aaudio_sharing_mode_t sharingMode, aaudio_performance_mode_t perfMode);
- ~StreamBuilderHelper();
-
- typedef aaudio_result_t (StreamCommand)(AAudioStream*);
- void streamCommand(
- StreamCommand cmd, aaudio_stream_state_t fromState, aaudio_stream_state_t toState);
-
- static const std::map<aaudio_performance_mode_t, int64_t> sMaxFramesPerBurstMs;
- const aaudio_direction_t mDirection;
- const Parameters mRequested;
- Parameters mActual;
- int32_t mFramesPerBurst;
- AAudioStreamBuilder *mBuilder;
- AAudioStream *mStream;
-};
-
-class InputStreamBuilderHelper : public StreamBuilderHelper {
- public:
- InputStreamBuilderHelper(
- aaudio_sharing_mode_t requestedSharingMode,
- aaudio_performance_mode_t requestedPerfMode);
- void createAndVerifyStream(bool *success);
-};
-
-class OutputStreamBuilderHelper : public StreamBuilderHelper {
- public:
- OutputStreamBuilderHelper(
- aaudio_sharing_mode_t requestedSharingMode,
- aaudio_performance_mode_t requestedPerfMode);
- void initBuilder();
- void createAndVerifyStream(bool *success);
-
- private:
- const int32_t kBufferCapacityFrames = 2000;
-};
-
-#endif // CTS_MEDIA_TEST_AAUDIO_UTILS_H
diff --git a/tests/tests/nativemedia/sl/AndroidTest.xml b/tests/tests/nativemedia/sl/AndroidTest.xml
index 0677a62..cfb0cae 100644
--- a/tests/tests/nativemedia/sl/AndroidTest.xml
+++ b/tests/tests/nativemedia/sl/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Native Media Open SL ES test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/nativemedia/xa/AndroidTest.xml b/tests/tests/nativemedia/xa/AndroidTest.xml
index c63c1ab..ae07686 100644
--- a/tests/tests/nativemedia/xa/AndroidTest.xml
+++ b/tests/tests/nativemedia/xa/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Native Media OpenMax AL test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/ndef/AndroidTest.xml b/tests/tests/ndef/AndroidTest.xml
index ca949bd..aef105f 100644
--- a/tests/tests/ndef/AndroidTest.xml
+++ b/tests/tests/ndef/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS NDEF test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="systems" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/net/Android.mk b/tests/tests/net/Android.mk
index 4aeab38..1d4d54b 100644
--- a/tests/tests/net/Android.mk
+++ b/tests/tests/net/Android.mk
@@ -24,7 +24,12 @@
# Include both the 32 and 64 bit versions
LOCAL_MULTILIB := both
-LOCAL_JAVA_LIBRARIES := voip-common conscrypt org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ voip-common \
+ conscrypt \
+ org.apache.http.legacy \
+ android.test.base \
+
LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libnativedns_jni \
libnativemultinetwork_jni libnativehelper_compat_libc++
@@ -40,8 +45,7 @@
ctstestrunner \
ctstestserver \
mockwebserver \
- junit \
- legacy-android-test
+ junit
# uncomment when b/13249961 is fixed
#LOCAL_SDK_VERSION := current
diff --git a/tests/tests/net/AndroidManifest.xml b/tests/tests/net/AndroidManifest.xml
index dd310a1..37bf323 100644
--- a/tests/tests/net/AndroidManifest.xml
+++ b/tests/tests/net/AndroidManifest.xml
@@ -30,7 +30,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/tests/tests/net/AndroidTest.xml b/tests/tests/net/AndroidTest.xml
index 4a578ea..4190a77 100644
--- a/tests/tests/net/AndroidTest.xml
+++ b/tests/tests/net/AndroidTest.xml
@@ -13,6 +13,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Net test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/net/native/qtaguid/AndroidTest.xml b/tests/tests/net/native/qtaguid/AndroidTest.xml
index 2eea82e..7591c87 100644
--- a/tests/tests/net/native/qtaguid/AndroidTest.xml
+++ b/tests/tests/net/native/qtaguid/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Native Network xt_qtaguid test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index e98dfcc..27dd666 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -16,8 +16,11 @@
package android.net.cts;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.app.PendingIntent;
@@ -29,6 +32,7 @@
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
@@ -50,14 +54,23 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.Socket;
import java.net.InetSocketAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import libcore.io.Streams;
public class ConnectivityManagerTest extends AndroidTestCase {
@@ -107,6 +120,8 @@
private final HashMap<Integer, NetworkConfig> mNetworks =
new HashMap<Integer, NetworkConfig>();
boolean mWifiConnectAttempted;
+ private TestNetworkCallback mCellNetworkCallback;
+
@Override
protected void setUp() throws Exception {
@@ -140,6 +155,10 @@
if (mWifiConnectAttempted) {
disconnectFromWifi(null);
}
+ if (cellConnectAttempted()) {
+ disconnectFromCell();
+ }
+ super.tearDown();
}
/**
@@ -246,6 +265,95 @@
}
}
+ /**
+ * Tests that connections can be opened on WiFi and cellphone networks,
+ * and that they are made from different IP addresses.
+ */
+ public void testOpenConnection() throws Exception {
+ boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+ && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+ if (!canRunTest) {
+ Log.i(TAG,"testOpenConnection cannot execute unless device supports both WiFi "
+ + "and a cellular connection");
+ return;
+ }
+
+ Network wifiNetwork = connectToWifi();
+ Network cellNetwork = connectToCell();
+ // This server returns the requestor's IP address as the response body.
+ URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
+ String wifiAddressString = httpGet(wifiNetwork, url);
+ String cellAddressString = httpGet(cellNetwork, url);
+
+ assertFalse(String.format("Same address '%s' on two different networks (%s, %s)",
+ wifiAddressString, wifiNetwork, cellNetwork),
+ wifiAddressString.equals(cellAddressString));
+
+ // Sanity check that the IP addresses that the requests appeared to come from
+ // are actually on the respective networks.
+ assertOnNetwork(wifiAddressString, wifiNetwork);
+ assertOnNetwork(cellAddressString, cellNetwork);
+
+ assertFalse("Unexpectedly equal: " + wifiNetwork, wifiNetwork.equals(cellNetwork));
+ }
+
+ private Network connectToCell() throws InterruptedException {
+ if (cellConnectAttempted()) {
+ throw new IllegalStateException("Already connected");
+ }
+ NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ mCellNetworkCallback = new TestNetworkCallback();
+ mCm.requestNetwork(cellRequest, mCellNetworkCallback);
+ final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
+ assertNotNull("Cell network not available within timeout", cellNetwork);
+ return cellNetwork;
+ }
+
+ private boolean cellConnectAttempted() {
+ return mCellNetworkCallback != null;
+ }
+
+ private void disconnectFromCell() {
+ if (!cellConnectAttempted()) {
+ throw new IllegalStateException("Cell connection not attempted");
+ }
+ mCm.unregisterNetworkCallback(mCellNetworkCallback);
+ mCellNetworkCallback = null;
+ }
+
+ /**
+ * Performs a HTTP GET to the specified URL on the specified Network, and returns
+ * the response body decoded as UTF-8.
+ */
+ private static String httpGet(Network network, URL httpUrl) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) network.openConnection(httpUrl);
+ try {
+ InputStream inputStream = connection.getInputStream();
+ return Streams.readFully(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ private void assertOnNetwork(String adressString, Network network) throws UnknownHostException {
+ InetAddress address = InetAddress.getByName(adressString);
+ LinkProperties linkProperties = mCm.getLinkProperties(network);
+ // To make sure that the request went out on the right network, check that
+ // the IP address seen by the server is assigned to the expected network.
+ // We can only do this for IPv6 addresses, because in IPv4 we will likely
+ // have a private IPv4 address, and that won't match what the server sees.
+ if (address instanceof Inet6Address) {
+ assertContains(linkProperties.getAddresses(), address);
+ }
+ }
+
+ private static<T> void assertContains(Collection<T> collection, T element) {
+ assertTrue(element + " not found in " + collection, collection.contains(element));
+ }
+
private void assertStartUsingNetworkFeatureUnsupported(int networkType, String feature) {
try {
mCm.startUsingNetworkFeature(networkType, feature);
@@ -324,7 +432,7 @@
* that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
*/
public void testRegisterNetworkCallback() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
return;
}
@@ -364,7 +472,7 @@
* of a {@code NetworkCallback}.
*/
public void testRegisterNetworkCallback_withPendingIntent() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
return;
}
@@ -458,7 +566,7 @@
* Tests reporting of connectivity changed.
*/
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
return;
}
@@ -475,7 +583,7 @@
}
public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
return;
}
@@ -495,7 +603,7 @@
public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
throws InterruptedException {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
return;
}
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
index 4c68423..55fa4d1 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
@@ -22,9 +22,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- ctstestserver \
- org.apache.http.legacy \
- legacy-android-test
+ ctstestserver
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src common)
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml
index 49385f8..d4ce39a 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
index 584afd2..033d7ea 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
@@ -22,9 +22,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- ctstestserver \
- org.apache.http.legacy \
- legacy-android-test
+ ctstestserver
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src common)
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml
index be698f2..fe31e80 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
index 9a613e7..c10a19a 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
@@ -22,9 +22,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- ctstestserver \
- org.apache.http.legacy \
- legacy-android-test
+ ctstestserver
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src common)
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml
index 7bd8742..c6b65c0 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk
index 10018ce..4af2de7 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
@@ -31,4 +33,4 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml
index 9356074..972052e 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigAttributeTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
index e6ee7c6..cfad3e4 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigAttributeTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk
index 161dbd3..95e14ef 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
@@ -31,4 +33,4 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml
index 565f23a..3e5fe25 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigBasicDomainConfigTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
index 36eb72a..bd43bac 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigBasicDomainConfigTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
new file mode 100644
index 0000000..6dc6c5d
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsNetSecConfigPrePCleartextTrafficTestCases
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res/
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_SDK_VERSION := 26
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidManifest.xml
new file mode 100644
index 0000000..bec926e
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.security.net.config.cts.CtsNetSecConfigPrePCleartextTrafficTestCases">
+ <application android:networkSecurityConfig="@xml/network_security_config">
+ <uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
+ </application>
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.security.net.config.cts.CtsNetSecConfigPrePCleartextTrafficTestCases"
+ android:label="">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
new file mode 100644
index 0000000..cde3314
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS CtsNetSecConfigPrePCleartextTraffic test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="security" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetSecConfigPrePCleartextTrafficTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.security.net.config.cts.CtsNetSecConfigPrePCleartextTrafficTestCases" />
+ <option name="runtime-hint" value="8m10s" />
+ </test>
+</configuration>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/res/xml/network_security_config.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/res/xml/network_security_config.xml
new file mode 100644
index 0000000..987b178
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="false">
+ <domain includeSubdomains="true">android.com</domain>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain>developer.android.com</domain>
+ </domain-config>
+ </domain-config>
+</network-security-config>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/src/android/security/net/config/cts/CleartextPermittedTest.java b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/src/android/security/net/config/cts/CleartextPermittedTest.java
new file mode 100644
index 0000000..b4b400c
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/src/android/security/net/config/cts/CleartextPermittedTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config.cts;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.X509TrustManager;
+import junit.framework.TestCase;
+
+public class CleartextPermittedTest extends TestCase {
+ public void testDefaultAllowed() throws Exception {
+ TestUtils.assertCleartextConnectionSucceeds("example.com", 80);
+ TestUtils.assertTlsConnectionSucceeds("example.com", 443);
+ }
+
+ public void testCleartextBlocked() throws Exception {
+ TestUtils.assertCleartextConnectionFails("android.com", 80);
+ TestUtils.assertTlsConnectionSucceeds("android.com", 443);
+ // subdomains of android.com are also disallowed.
+ TestUtils.assertCleartextConnectionFails("www.android.com", 80);
+ TestUtils.assertTlsConnectionSucceeds("www.android.com", 443);
+ }
+
+ public void testNestedCleartextPermitted() throws Exception {
+ // developer.android.com is explicitly permitted.
+ TestUtils.assertCleartextConnectionSucceeds("developer.android.com", 80);
+ TestUtils.assertTlsConnectionSucceeds("developer.android.com", 443);
+ }
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk
index 927374c..278d634 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml
index b387ffa..8ee5482 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigCleartextTrafficTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
index f2e3f35..d5c33be 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigCleartextTraffic test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml
index 987b178..6d41bba 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/res/xml/network_security_config.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
- <domain-config cleartextTrafficPermitted="false">
+ <domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">android.com</domain>
- <domain-config cleartextTrafficPermitted="true">
+ <domain-config cleartextTrafficPermitted="false">
<domain>developer.android.com</domain>
</domain-config>
</domain-config>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java
index db8e40f..9592178 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/src/android/security/net/config/cts/CleartextPermittedTest.java
@@ -22,22 +22,22 @@
import junit.framework.TestCase;
public class CleartextPermittedTest extends TestCase {
- public void testDefaultAllowed() throws Exception {
- TestUtils.assertCleartextConnectionSucceeds("example.com", 80);
+ public void testDefaultDenied() throws Exception {
+ TestUtils.assertCleartextConnectionFails("example.com", 80);
TestUtils.assertTlsConnectionSucceeds("example.com", 443);
}
- public void testCleartextBlocked() throws Exception {
- TestUtils.assertCleartextConnectionFails("android.com", 80);
+ public void testCleartextAllowed() throws Exception {
+ TestUtils.assertCleartextConnectionSucceeds("android.com", 80);
TestUtils.assertTlsConnectionSucceeds("android.com", 443);
// subdomains of android.com are also disallowed.
- TestUtils.assertCleartextConnectionFails("www.android.com", 80);
+ TestUtils.assertCleartextConnectionSucceeds("www.android.com", 80);
TestUtils.assertTlsConnectionSucceeds("www.android.com", 443);
}
- public void testNestedCleartextPermitted() throws Exception {
- // developer.android.com is explicitly permitted.
- TestUtils.assertCleartextConnectionSucceeds("developer.android.com", 80);
+ public void testNestedCleartextDenied() throws Exception {
+ // developer.android.com is explicitly denied.
+ TestUtils.assertCleartextConnectionFails("developer.android.com", 80);
TestUtils.assertTlsConnectionSucceeds("developer.android.com", 443);
}
}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk
index aa0eefd..fd5f419 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
@@ -31,4 +33,4 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
-include $(BUILD_CTS_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml
index 3f15f81..28bba5b 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidManifest.xml
@@ -21,6 +21,7 @@
<application android:debuggable="false"
android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
index 14e4eb4..666ed68 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigBasicDebugDisabledTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk
index be9174e..d808928 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml
index 6d98702..b667271 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidManifest.xml
@@ -21,6 +21,7 @@
<application android:debuggable="true"
android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
index 198f30e..a92726e 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigBasicDebugEnabledTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
index 84e72b0..4a40d2a 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
@@ -22,9 +22,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- org.apache.http.legacy \
- android-support-test \
- legacy-android-test
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
index e18ff4d..cc67cca 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
index fb26391..0d1b2a2 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigDownloadManagerTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk
index 4764cab..3d66c53 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml
index b3b32b5..bf6e369 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigInvalidPinTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
index 7f3c7fd..c2c54f3 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigInvalidPinTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk
index 5448f34..e96ae6a 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml
index 1790150..75247d3 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigNestedDomainConfigTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
index 0209025..ca37091 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigNestedDomainConfigTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
index 924f393..48bbfaf 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
@@ -22,9 +22,13 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := \
org.apache.http.legacy \
- android-support-test \
- legacy-android-test
+ android.test.runner.stubs \
+ android.test.base.stubs \
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml
index 46787160..4884458 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidManifest.xml
@@ -20,6 +20,7 @@
package="android.security.net.config.cts.CtsNetSecConfigResourcesSrcTestCases">
<application android:networkSecurityConfig="@xml/network_security_config">
<uses-library android:name="android.test.runner"/>
+ <uses-library android:name="org.apache.http.legacy" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
index 671633e..1dd8db7 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS CtsNetSecConfigInvalidPinTestCases test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/neuralnetworks/AndroidTest.xml b/tests/tests/neuralnetworks/AndroidTest.xml
index cd1a0f0..131db6a 100644
--- a/tests/tests/neuralnetworks/AndroidTest.xml
+++ b/tests/tests/neuralnetworks/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Native NNAPI Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="neuralnetworks" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/opengl/Android.mk b/tests/tests/opengl/Android.mk
index d623932..f9a36eb 100644
--- a/tests/tests/opengl/Android.mk
+++ b/tests/tests/opengl/Android.mk
@@ -29,7 +29,9 @@
LOCAL_JNI_SHARED_LIBRARIES := libopengltest_jni
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/opengl/AndroidTest.xml b/tests/tests/opengl/AndroidTest.xml
index fc24223..4414182 100644
--- a/tests/tests/opengl/AndroidTest.xml
+++ b/tests/tests/opengl/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS OpenGL test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="graphics" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
index 4225de0..24b0f6a 100644
--- a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
@@ -191,7 +191,7 @@
"EGL_KHR_wait_sync",
};
- for (int i = 0; i < requiredList.length; ++i) {
+ for (int i = 0; i < requiredEglList.length; ++i) {
assertTrue("Required EGL extension for VR high-performance is missing: " +
requiredEglList[i], hasExtension(extensions, requiredEglList[i]));
}
diff --git a/tests/tests/openglperf/Android.mk b/tests/tests/openglperf/Android.mk
index 7669204..78a25fc 100644
--- a/tests/tests/openglperf/Android.mk
+++ b/tests/tests/openglperf/Android.mk
@@ -26,6 +26,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_JNI_SHARED_LIBRARIES := libctsopenglperf_jni libnativehelper_compat_libc++
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/openglperf/AndroidTest.xml b/tests/tests/openglperf/AndroidTest.xml
index dc6b2a8..48cceec 100644
--- a/tests/tests/openglperf/AndroidTest.xml
+++ b/tests/tests/openglperf/AndroidTest.xml
@@ -13,6 +13,7 @@
limitations under the License.
-->
<configuration description="Config for CTS OpenGL Performance test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="graphics" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index 1aa46cc..7984227 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -28,9 +28,9 @@
android-support-test \
compatibility-device-util \
ctstestrunner \
+ truth-prebuilt \
guava \
- junit \
- legacy-android-test
+ junit
LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libctsos_jni libnativehelper_compat_libc++
@@ -50,6 +50,7 @@
# uncomment when b/13282254 is fixed
#LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.base
# Do not compress minijail policy files.
LOCAL_AAPT_FLAGS := -0 .policy
diff --git a/tests/tests/os/AndroidTest.xml b/tests/tests/os/AndroidTest.xml
index 27f235a..b66c292 100644
--- a/tests/tests/os/AndroidTest.xml
+++ b/tests/tests/os/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for OS Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index f62b470..7336fc0 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -16,13 +16,16 @@
package android.os.cts;
+import static android.os.Build.VERSION.CODENAME;
+import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
import android.os.Build;
-import android.os.SystemProperties;
import android.platform.test.annotations.RestrictedBuildTest;
import dalvik.system.VMRuntime;
+import junit.framework.TestCase;
+
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -31,11 +34,6 @@
import java.util.Scanner;
import java.util.regex.Pattern;
-import junit.framework.TestCase;
-
-import static android.os.Build.VERSION.CODENAME;
-import static android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
-
public class BuildTest extends TestCase {
private static final String RO_PRODUCT_CPU_ABILIST = "ro.product.cpu.abilist";
@@ -254,6 +252,25 @@
}
}
+ /**
+ * Verify that SDK versions are bounded by both high and low expected
+ * values.
+ */
+ public void testSdkInt() {
+ assertTrue(
+ "Current SDK version " + Build.VERSION.SDK_INT
+ + " is invalid; must be at least VERSION_CODES.BASE",
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE);
+ assertTrue(
+ "First SDK version " + Build.VERSION.FIRST_SDK_INT
+ + " is invalid; must be at least VERSION_CODES.BASE",
+ Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.BASE);
+ assertTrue(
+ "Current SDK version " + Build.VERSION.SDK_INT
+ + " must be at least first SDK version " + Build.VERSION.FIRST_SDK_INT,
+ Build.VERSION.SDK_INT >= Build.VERSION.FIRST_SDK_INT);
+ }
+
static final String RO_DEBUGGABLE = "ro.debuggable";
private static final String RO_SECURE = "ro.secure";
diff --git a/tests/tests/os/src/android/os/cts/CtsRemoteService.java b/tests/tests/os/src/android/os/cts/CtsRemoteService.java
index daae49e..a0d5974 100644
--- a/tests/tests/os/src/android/os/cts/CtsRemoteService.java
+++ b/tests/tests/os/src/android/os/cts/CtsRemoteService.java
@@ -20,8 +20,10 @@
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
+import java.io.File;
+import java.io.IOException;
-public class CtsRemoteService extends Service{
+public class CtsRemoteService extends Service {
@Override
public void onCreate() {
@@ -41,6 +43,15 @@
public String getTimeZoneID() {
return java.util.TimeZone.getDefault().getID();
}
+
+ public boolean performDiskWrite() {
+ try {
+ File tempFile = File.createTempFile("foo", "bar");
+ return tempFile.delete();
+ } catch (IOException exception) {
+ return false;
+ }
+ }
};
@Override
@@ -50,5 +61,4 @@
}
return null;
}
-
}
diff --git a/tests/tests/os/src/android/os/cts/ISecondary.aidl b/tests/tests/os/src/android/os/cts/ISecondary.aidl
index 2c60149..183e2d5 100644
--- a/tests/tests/os/src/android/os/cts/ISecondary.aidl
+++ b/tests/tests/os/src/android/os/cts/ISecondary.aidl
@@ -23,4 +23,6 @@
long getElapsedCpuTime();
String getTimeZoneID();
+
+ boolean performDiskWrite();
}
diff --git a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
index 4679a99..38651d7 100644
--- a/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelFileDescriptorTest.java
@@ -16,6 +16,13 @@
package android.os.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
@@ -24,13 +31,20 @@
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.Parcelable;
import android.os.cts.ParcelFileDescriptorPeer.FutureCloseListener;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.test.MoreAsserts;
import com.google.common.util.concurrent.AbstractFuture;
import junit.framework.ComparisonFailure;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -43,9 +57,15 @@
import java.net.Socket;
import java.util.concurrent.TimeUnit;
-public class ParcelFileDescriptorTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ParcelFileDescriptorTest {
private static final long DURATION = 100l;
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Test
public void testConstructorAndOpen() throws Exception {
ParcelFileDescriptor tempFile = makeParcelFileDescriptor(getContext());
@@ -73,6 +93,7 @@
}
}
+ @Test
public void testFromSocket() throws Throwable {
final int PORT = 12222;
final int DATA = 1;
@@ -111,6 +132,7 @@
done.get(5, TimeUnit.SECONDS);
}
+ @Test
public void testFromData() throws IOException {
assertNull(ParcelFileDescriptor.fromData(null, null));
byte[] data = new byte[] { 0 };
@@ -143,6 +165,7 @@
}
}
+ @Test
public void testFromDataSkip() throws IOException {
byte[] data = new byte[] { 40, 41, 42, 43, 44, 45, 46 };
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null);
@@ -164,11 +187,13 @@
}
}
+ @Test
public void testToString() {
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
assertNotNull(pfd.toString());
}
+ @Test
public void testWriteToParcel() throws Exception {
ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext());
@@ -188,6 +213,7 @@
}
}
+ @Test
public void testClose() throws Exception {
ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext());
AutoCloseInputStream in1 = new AutoCloseInputStream(pf);
@@ -210,11 +236,13 @@
}
}
+ @Test
public void testGetStatSize() throws Exception {
ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext());
assertTrue(pf.getStatSize() >= 0);
}
+ @Test
public void testGetFileDescriptor() {
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
assertNotNull(pfd.getFileDescriptor());
@@ -223,6 +251,7 @@
assertSame(pfd.getFileDescriptor(), p.getFileDescriptor());
}
+ @Test
public void testDescribeContents() {
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
assertTrue((Parcelable.CONTENTS_FILE_DESCRIPTOR & pfd.describeContents()) != 0);
@@ -247,6 +276,7 @@
return new FileInputStream(pfd.getFileDescriptor()).read();
}
+ @Test
public void testPipeNormal() throws Exception {
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
final ParcelFileDescriptor red = pipe[0];
@@ -262,6 +292,7 @@
// Reading should be done via AutoCloseInputStream if possible, rather than
// recreating a FileInputStream from a raw FD, what's done in read(PFD).
+ @Test
public void testPipeError_Discouraged() throws Exception {
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
final ParcelFileDescriptor red = pipe[0];
@@ -281,6 +312,7 @@
}
}
+ @Test
public void testPipeError() throws Exception {
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
final ParcelFileDescriptor red = pipe[0];
@@ -298,6 +330,7 @@
}
}
+ @Test
public void testFileNormal() throws Exception {
final Handler handler = new Handler(Looper.getMainLooper());
final FutureCloseListener listener = new FutureCloseListener();
@@ -312,6 +345,7 @@
assertEquals(null, listener.get());
}
+ @Test
public void testFileError() throws Exception {
final Handler handler = new Handler(Looper.getMainLooper());
final FutureCloseListener listener = new FutureCloseListener();
@@ -326,6 +360,7 @@
assertContains("OMG BANANAS", listener.get().getMessage());
}
+ @Test
public void testFileDetach() throws Exception {
final Handler handler = new Handler(Looper.getMainLooper());
final FutureCloseListener listener = new FutureCloseListener();
@@ -339,6 +374,7 @@
assertContains("DETACHED", listener.get().getMessage());
}
+ @Test
public void testSocketErrorAfterClose() throws Exception {
final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair();
final ParcelFileDescriptor red = pair[0];
@@ -361,6 +397,7 @@
blue.checkError();
}
+ @Test
public void testSocketMultipleCheck() throws Exception {
final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair();
final ParcelFileDescriptor red = pair[0];
@@ -382,6 +419,7 @@
}
// http://b/21578056
+ @Test
public void testFileNamesWithNonBmpChars() throws Exception {
final File file = File.createTempFile("treble_clef_\ud834\udd1e", ".tmp");
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
@@ -390,6 +428,57 @@
pfd.close();
}
+ @Test
+ public void testCheckFinalizerBehavior() throws Exception {
+ final Runtime runtime = Runtime.getRuntime();
+ ParcelFileDescriptor pfd = makeParcelFileDescriptor(getContext());
+ assertTrue(checkIsValid(pfd.getFileDescriptor()));
+
+ ParcelFileDescriptor wrappedPfd = new ParcelFileDescriptor(pfd);
+ assertTrue(checkIsValid(wrappedPfd.getFileDescriptor()));
+
+ FileDescriptor fd = pfd.getFileDescriptor();
+ int rawFd = pfd.getFd();
+ pfd = null;
+ assertNull(pfd); // To keep tools happy - yes we are using the write to null
+ runtime.gc(); runtime.runFinalization();
+ assertTrue("Wrapped PFD failed to hold reference",
+ checkIsValid(wrappedPfd.getFileDescriptor()));
+ assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd));
+
+ wrappedPfd = null;
+ assertNull(wrappedPfd); // To keep tools happy - yes we are using the write to null
+ runtime.gc(); runtime.runFinalization();
+ // TODO: Enable this once b/65027998 is fixed
+ //assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd));
+
+ fd = null;
+ assertNull(fd); // To keep tools happy - yes we are using the write to null
+ runtime.gc(); runtime.runFinalization();
+
+ try {
+ ParcelFileDescriptor.fromFd(rawFd);
+ fail("FD leaked");
+ } catch (IOException ex) {
+ // Success
+ }
+ }
+
+ boolean checkIsValid(FileDescriptor fd) {
+ try {
+ Os.fstat(fd);
+ return true;
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.EBADF) {
+ return false;
+ } else {
+ fail(e.getMessage());
+ // not reached
+ return false;
+ }
+ }
+ }
+
static ParcelFileDescriptor makeParcelFileDescriptor(Context con) throws Exception {
final String fileName = "testParcelFileDescriptor";
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 21730c6..6cf3ded 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -16,340 +16,473 @@
package android.os.cts;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.TrafficStats;
import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.os.StrictMode;
-import android.os.StrictMode.ViolationListener;
+import android.os.StrictMode.ThreadPolicy.Builder;
+import android.os.StrictMode.ViolationInfo;
+import android.os.strictmode.UntaggedSocketViolation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.system.Os;
import android.system.OsConstants;
-import android.test.InstrumentationTestCase;
import android.util.Log;
-
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-/**
- * Tests for {@link StrictMode}
- */
-public class StrictModeTest extends InstrumentationTestCase {
+/** Tests for {@link StrictMode} */
+@RunWith(AndroidJUnit4.class)
+public class StrictModeTest {
private static final String TAG = "StrictModeTest";
+ private static final String REMOTE_SERVICE_ACTION = "android.app.REMOTESERVICE";
private StrictMode.ThreadPolicy mThreadPolicy;
private StrictMode.VmPolicy mVmPolicy;
private Context getContext() {
- return getInstrumentation().getContext();
+ return InstrumentationRegistry.getContext();
}
- @Override
- protected void setUp() {
+ @Before
+ public void setUp() {
mThreadPolicy = StrictMode.getThreadPolicy();
mVmPolicy = StrictMode.getVmPolicy();
}
- @Override
- protected void tearDown() {
+ @After
+ public void tearDown() {
StrictMode.setThreadPolicy(mThreadPolicy);
StrictMode.setVmPolicy(mVmPolicy);
}
public interface ThrowingRunnable {
- public void run() throws Exception;
+ void run() throws Exception;
}
- /**
- * Insecure connection should be detected
- */
+ @Test
+ public void testUnclosedCloseable() throws Exception {
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectLeakedClosableObjects().build());
+
+ inspectViolation(
+ () -> leakCloseable("leaked.txt"),
+ info -> {
+ assertThat(info.getViolationDetails())
+ .isEqualTo(
+ "A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.");
+ assertThat(info.getStackTrace())
+ .contains("Explicit termination method 'close' not called");
+ assertThat(info.getStackTrace()).contains("leakCloseable");
+ assertPolicy(info, StrictMode.DETECT_VM_CLOSABLE_LEAKS);
+ });
+ }
+
+ private void leakCloseable(String fileName) throws InterruptedException {
+ final CountDownLatch finalizedSignal = new CountDownLatch(1);
+ try {
+ new FileOutputStream(new File(getContext().getFilesDir(), fileName)) {
+ @Override
+ protected void finalize() throws IOException {
+ super.finalize();
+ finalizedSignal.countDown();
+ }
+ };
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ // Sometimes it needs extra prodding.
+ if (!finalizedSignal.await(5, TimeUnit.SECONDS)) {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ }
+ }
+
+ @Test
+ public void testClassInstanceLimit() throws Exception {
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder()
+ .setClassInstanceLimit(LimitedClass.class, 1)
+ .build());
+ List<LimitedClass> references = new ArrayList<>();
+ assertNoViolation(() -> references.add(new LimitedClass()));
+ references.add(new LimitedClass());
+ inspectViolation(
+ StrictMode::conditionallyCheckInstanceCounts,
+ info -> assertPolicy(info, StrictMode.DETECT_VM_INSTANCE_LEAKS));
+ }
+
+ private static final class LimitedClass {}
+
+ /** Insecure connection should be detected */
+ @Test
public void testCleartextNetwork() throws Exception {
if (!hasInternetConnection()) {
Log.i(TAG, "testCleartextNetwork() ignored on device without Internet");
return;
}
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectCleartextNetwork()
- .penaltyLog()
- .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
- assertViolation("Detected cleartext network traffic from UID", () -> {
- ((HttpURLConnection) new URL("http://example.com/").openConnection())
- .getResponseCode();
- });
+ inspectViolation(
+ () ->
+ ((HttpURLConnection) new URL("http://example.com/").openConnection())
+ .getResponseCode(),
+ info -> {
+ assertThat(info.getViolationDetails())
+ .contains("Detected cleartext network traffic from UID");
+ assertThat(info.getViolationDetails())
+ .startsWith(StrictMode.CLEARTEXT_DETECTED_MSG);
+ assertPolicy(info, StrictMode.DETECT_VM_CLEARTEXT_NETWORK);
+ });
}
- /**
- * Secure connection should be ignored
- */
+ /** Secure connection should be ignored */
+ @Test
public void testEncryptedNetwork() throws Exception {
if (!hasInternetConnection()) {
Log.i(TAG, "testEncryptedNetwork() ignored on device without Internet");
return;
}
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectCleartextNetwork()
- .penaltyLog()
- .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
- assertNoViolation(() -> {
- ((HttpURLConnection) new URL("https://example.com/").openConnection())
- .getResponseCode();
- });
+ assertNoViolation(
+ () ->
+ ((HttpURLConnection) new URL("https://example.com/").openConnection())
+ .getResponseCode());
}
+ @Test
public void testFileUriExposure() throws Exception {
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectFileUriExposure()
- .penaltyLog()
- .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectFileUriExposure().penaltyLog().build());
final Uri badUri = Uri.fromFile(new File("/sdcard/meow.jpg"));
- assertViolation(badUri + " exposed beyond app", () -> {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setDataAndType(badUri, "image/jpeg");
- getContext().startActivity(intent);
- });
+ inspectViolation(
+ () -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(badUri, "image/jpeg");
+ getContext().startActivity(intent);
+ },
+ violation -> {
+ assertThat(violation.getStackTrace()).contains(badUri + " exposed beyond app");
+ });
final Uri goodUri = Uri.parse("content://com.example/foobar");
- assertNoViolation(() -> {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setDataAndType(goodUri, "image/jpeg");
- getContext().startActivity(intent);
- });
+ assertNoViolation(
+ () -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(goodUri, "image/jpeg");
+ getContext().startActivity(intent);
+ });
}
+ @Test
public void testContentUriWithoutPermission() throws Exception {
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectContentUriWithoutPermission()
- .penaltyLog()
- .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder()
+ .detectContentUriWithoutPermission()
+ .penaltyLog()
+ .build());
final Uri uri = Uri.parse("content://com.example/foobar");
- assertViolation(uri + " exposed beyond app", () -> {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setDataAndType(uri, "image/jpeg");
- getContext().startActivity(intent);
- });
+ inspectViolation(
+ () -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(uri, "image/jpeg");
+ getContext().startActivity(intent);
+ },
+ violation ->
+ assertThat(violation.getStackTrace())
+ .contains(uri + " exposed beyond app"));
- assertNoViolation(() -> {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setDataAndType(uri, "image/jpeg");
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- getContext().startActivity(intent);
- });
+ assertNoViolation(
+ () -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(uri, "image/jpeg");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ getContext().startActivity(intent);
+ });
}
+ @Test
public void testUntaggedSocketsHttp() throws Exception {
if (!hasInternetConnection()) {
Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
return;
}
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectUntaggedSockets()
- .penaltyLog()
- .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectUntaggedSockets().penaltyLog().build());
- assertViolation("Untagged socket detected", () -> {
- ((HttpURLConnection) new URL("http://example.com/").openConnection())
- .getResponseCode();
- });
+ inspectViolation(
+ () ->
+ ((HttpURLConnection) new URL("http://example.com/").openConnection())
+ .getResponseCode(),
+ violation ->
+ assertThat(violation.getStackTrace())
+ .contains(UntaggedSocketViolation.MESSAGE));
- assertNoViolation(() -> {
- TrafficStats.setThreadStatsTag(0xDECAFBAD);
- try {
- ((HttpURLConnection) new URL("http://example.com/").openConnection())
- .getResponseCode();
- } finally {
- TrafficStats.clearThreadStatsTag();
- }
- });
+ assertNoViolation(
+ () -> {
+ TrafficStats.setThreadStatsTag(0xDECAFBAD);
+ try {
+ ((HttpURLConnection) new URL("http://example.com/").openConnection())
+ .getResponseCode();
+ } finally {
+ TrafficStats.clearThreadStatsTag();
+ }
+ });
}
+ @Test
public void testUntaggedSocketsRaw() throws Exception {
if (!hasInternetConnection()) {
Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
return;
}
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectUntaggedSockets()
- .penaltyLog()
- .build());
+ StrictMode.setVmPolicy(
+ new StrictMode.VmPolicy.Builder().detectUntaggedSockets().penaltyLog().build());
- assertNoViolation(() -> {
- TrafficStats.setThreadStatsTag(0xDECAFBAD);
- try (Socket socket = new Socket("example.com", 80)) {
- socket.getOutputStream().close();
- } finally {
- TrafficStats.clearThreadStatsTag();
- }
- });
+ assertNoViolation(
+ () -> {
+ TrafficStats.setThreadStatsTag(0xDECAFBAD);
+ try (Socket socket = new Socket("example.com", 80)) {
+ socket.getOutputStream().close();
+ } finally {
+ TrafficStats.clearThreadStatsTag();
+ }
+ });
- assertViolation("Untagged socket detected", () -> {
- try (Socket socket = new Socket("example.com", 80)) {
- socket.getOutputStream().close();
- }
- });
+ inspectViolation(
+ () -> {
+ try (Socket socket = new Socket("example.com", 80)) {
+ socket.getOutputStream().close();
+ }
+ },
+ violation ->
+ assertThat(violation.getStackTrace())
+ .contains(UntaggedSocketViolation.MESSAGE));
}
+ private static final int PERMISSION_USER_ONLY = 0600;
+
+ @Test
public void testRead() throws Exception {
final File test = File.createTempFile("foo", "bar");
final File dir = test.getParentFile();
final FileInputStream is = new FileInputStream(test);
- final FileDescriptor fd = Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
+ final FileDescriptor fd =
+ Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, PERMISSION_USER_ONLY);
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectDiskReads()
- .penaltyLog()
- .build());
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build());
+ inspectViolation(
+ test::exists,
+ violation -> {
+ assertThat(violation.getViolationDetails()).isNull();
+ assertThat(violation.getStackTrace()).contains("DiskReadViolation");
+ });
- assertViolation("StrictModeDiskReadViolation", () -> {
- test.exists();
- });
- assertViolation("StrictModeDiskReadViolation", () -> {
- test.length();
- });
- assertViolation("StrictModeDiskReadViolation", () -> {
- dir.list();
- });
- assertViolation("StrictModeDiskReadViolation", () -> {
- new FileInputStream(test);
- });
- assertViolation("StrictModeDiskReadViolation", () -> {
- is.read();
- });
- assertViolation("StrictModeDiskReadViolation", () -> {
- Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
- });
- assertViolation("StrictModeDiskReadViolation", () -> {
- Os.read(fd, new byte[10], 0, 1);
- });
+ Consumer<ViolationInfo> assertDiskReadPolicy =
+ violation -> assertPolicy(violation, StrictMode.DETECT_DISK_READ);
+ inspectViolation(test::exists, assertDiskReadPolicy);
+ inspectViolation(test::length, assertDiskReadPolicy);
+ inspectViolation(dir::list, assertDiskReadPolicy);
+ inspectViolation(is::read, assertDiskReadPolicy);
+
+ inspectViolation(() -> new FileInputStream(test), assertDiskReadPolicy);
+ inspectViolation(
+ () -> Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, PERMISSION_USER_ONLY),
+ assertDiskReadPolicy);
+ inspectViolation(() -> Os.read(fd, new byte[10], 0, 1), assertDiskReadPolicy);
}
+ @Test
public void testWrite() throws Exception {
File file = File.createTempFile("foo", "bar");
final FileOutputStream os = new FileOutputStream(file);
- final FileDescriptor fd = Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, 0600);
+ final FileDescriptor fd =
+ Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, PERMISSION_USER_ONLY);
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectDiskWrites()
- .penaltyLog()
- .build());
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectDiskWrites().penaltyLog().build());
- assertViolation("StrictModeDiskWriteViolation", () -> {
- File.createTempFile("foo", "bar");
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- file.delete();
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- file.createNewFile();
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- new FileOutputStream(file);
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- os.write(32);
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, 0600);
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- Os.write(fd, new byte[10], 0, 1);
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- Os.fsync(fd);
- });
- assertViolation("StrictModeDiskWriteViolation", () -> {
- file.renameTo(new File(file.getParent(), "foobar"));
- });
+ inspectViolation(
+ file::createNewFile,
+ violation -> {
+ assertThat(violation.getViolationDetails()).isNull();
+ assertThat(violation.getStackTrace()).contains("DiskWriteViolation");
+ });
+
+ Consumer<ViolationInfo> assertDiskWritePolicy =
+ violation -> assertPolicy(violation, StrictMode.DETECT_DISK_WRITE);
+
+ inspectViolation(() -> File.createTempFile("foo", "bar"), assertDiskWritePolicy);
+ inspectViolation(() -> new FileOutputStream(file), assertDiskWritePolicy);
+ inspectViolation(file::delete, assertDiskWritePolicy);
+ inspectViolation(file::createNewFile, assertDiskWritePolicy);
+ inspectViolation(() -> os.write(32), assertDiskWritePolicy);
+
+ inspectViolation(
+ () -> Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, PERMISSION_USER_ONLY),
+ assertDiskWritePolicy);
+ inspectViolation(() -> Os.write(fd, new byte[10], 0, 1), assertDiskWritePolicy);
+ inspectViolation(() -> Os.fsync(fd), assertDiskWritePolicy);
+ inspectViolation(
+ () -> file.renameTo(new File(file.getParent(), "foobar")), assertDiskWritePolicy);
}
+ @Test
public void testNetwork() throws Exception {
if (!hasInternetConnection()) {
Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
return;
}
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectNetwork()
- .penaltyLog()
- .build());
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder().detectNetwork().penaltyLog().build());
- assertViolation("StrictModeNetworkViolation", () -> {
- try (Socket socket = new Socket("example.com", 80)) {
- socket.getOutputStream().close();
- }
- });
+ inspectViolation(
+ () -> {
+ try (Socket socket = new Socket("example.com", 80)) {
+ socket.getOutputStream().close();
+ }
+ },
+ violation -> assertPolicy(violation, StrictMode.DETECT_NETWORK));
+ inspectViolation(
+ () ->
+ ((HttpURLConnection) new URL("http://example.com/").openConnection())
+ .getResponseCode(),
+ violation -> assertPolicy(violation, StrictMode.DETECT_NETWORK));
+ }
- assertViolation("StrictModeNetworkViolation", () -> {
- ((HttpURLConnection) new URL("http://example.com/").openConnection())
- .getResponseCode();
- });
+ @Test
+ public void testViolationAcrossBinder() throws Exception {
+ runWithRemoteServiceBound(
+ getContext(),
+ service -> {
+ StrictMode.setThreadPolicy(
+ new Builder().detectDiskWrites().penaltyLog().build());
+
+ try {
+ inspectViolation(
+ () -> service.performDiskWrite(),
+ (violation) -> {
+ assertPolicy(violation, StrictMode.DETECT_DISK_WRITE);
+ assertThat(violation.getViolationDetails())
+ .isNull(); // Disk write has no message.
+ assertThat(violation.getStackTrace())
+ .contains("DiskWriteViolation");
+ assertThat(violation.getStackTrace())
+ .contains(
+ "at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk");
+ assertThat(violation.getStackTrace())
+ .contains("# via Binder call with stack:");
+ assertThat(violation.getStackTrace())
+ .contains(
+ "at android.os.cts.ISecondary$Stub$Proxy.performDiskWrite");
+ });
+ assertNoViolation(() -> service.getPid());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private static void runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)
+ throws ExecutionException, InterruptedException, RemoteException {
+ BlockingQueue<IBinder> binderHolder = new ArrayBlockingQueue<>(1);
+ ServiceConnection secondaryConnection =
+ new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ binderHolder.add(service);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ binderHolder.drainTo(new ArrayList<>());
+ }
+ };
+ Intent intent = new Intent(REMOTE_SERVICE_ACTION);
+ intent.setPackage(context.getPackageName());
+
+ Intent secondaryIntent = new Intent(ISecondary.class.getName());
+ secondaryIntent.setPackage(context.getPackageName());
+ assertThat(
+ context.bindService(
+ secondaryIntent, secondaryConnection, Context.BIND_AUTO_CREATE))
+ .isTrue();
+ IBinder binder = binderHolder.take();
+ assertThat(binder.queryLocalInterface(binder.getInterfaceDescriptor())).isNull();
+ consumer.accept(ISecondary.Stub.asInterface(binder));
+ context.unbindService(secondaryConnection);
+ context.stopService(intent);
}
private static void assertViolation(String expected, ThrowingRunnable r) throws Exception {
- final LinkedBlockingQueue<String> violations = new LinkedBlockingQueue<>();
- StrictMode.setViolationListener(new ViolationListener() {
- @Override
- public void onViolation(String message) {
- violations.add(message);
- }
- });
-
- try {
- r.run();
- while (true) {
- final String violation = violations.poll(5, TimeUnit.SECONDS);
- if (violation == null) {
- fail("Expected violation not found: " + expected);
- } else if (violation.contains(expected)) {
- return;
- }
- }
- } finally {
- StrictMode.setViolationListener(null);
- }
+ inspectViolation(r, violation -> assertThat(violation.getStackTrace()).contains(expected));
}
private static void assertNoViolation(ThrowingRunnable r) throws Exception {
- final LinkedBlockingQueue<String> violations = new LinkedBlockingQueue<>();
- StrictMode.setViolationListener(new ViolationListener() {
- @Override
- public void onViolation(String message) {
- violations.add(message);
- }
- });
+ inspectViolation(
+ r, violation -> assertWithMessage("Unexpected violation").that(violation).isNull());
+ }
+
+ private void assertPolicy(ViolationInfo info, int policy) {
+ assertWithMessage("Policy bit incorrect").that(info.getViolationBit()).isEqualTo(policy);
+ }
+
+ private static void inspectViolation(
+ ThrowingRunnable violating, Consumer<ViolationInfo> consume) throws Exception {
+ final LinkedBlockingQueue<ViolationInfo> violations = new LinkedBlockingQueue<>();
+ StrictMode.setViolationLogger(violations::add);
try {
- r.run();
- while (true) {
- final String violation = violations.poll(5, TimeUnit.SECONDS);
- if (violation == null) {
- return;
- } else {
- fail("Unexpected violation found: " + violation);
- }
- }
+ violating.run();
+ consume.accept(violations.poll(5, TimeUnit.SECONDS));
} finally {
- StrictMode.setViolationListener(null);
+ StrictMode.setViolationLogger(null);
}
}
diff --git a/tests/tests/os/src/android/os/cts/WorkSourceTest.java b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
index ff9d693..e7df7be 100644
--- a/tests/tests/os/src/android/os/cts/WorkSourceTest.java
+++ b/tests/tests/os/src/android/os/cts/WorkSourceTest.java
@@ -35,8 +35,6 @@
private Object[] mAddReturningNewbsArgs = new Object[1];
private Method mSetReturningDiffs;
private Object[] mSetReturningDiffsArgs = new Object[1];
- private Method mStripNames;
- private Object[] mStripNamesArgs = new Object[0];
@Override
protected void setUp() throws Exception {
@@ -46,7 +44,6 @@
mAddUidName = WorkSource.class.getMethod("add", new Class[] { int.class, String.class });
mAddReturningNewbs = WorkSource.class.getMethod("addReturningNewbs", new Class[] { WorkSource.class });
mSetReturningDiffs = WorkSource.class.getMethod("setReturningDiffs", new Class[] { WorkSource.class });
- mStripNames = WorkSource.class.getMethod("stripNames", new Class[] { });
}
private WorkSource wsNew(int uid) throws IllegalArgumentException,
@@ -100,11 +97,6 @@
return (WorkSource[])mSetReturningDiffs.invoke(ws, mSetReturningDiffsArgs);
}
- private WorkSource wsStripNames(WorkSource ws) throws IllegalArgumentException,
- InstantiationException, IllegalAccessException, InvocationTargetException {
- return (WorkSource)mStripNames.invoke(ws);
- }
-
private void printArrays(StringBuilder sb, int[] uids, String[] names) {
sb.append("{ ");
for (int i=0; i<uids.length; i++) {
@@ -529,31 +521,4 @@
new int[] { },
true);
}
-
- private void doTestStripNames(int[] uids, String[] names, int[] expected) throws Exception {
- WorkSource ws1 = wsNew(uids, names);
- WorkSource res = wsStripNames(ws1);
- checkWorkSource("StripNames", res, expected);
- }
-
- public void testStripNamesSimple() throws Exception {
- doTestStripNames(
- new int[] { 10, 20, 30, 40 },
- new String[] { "A", "A", "A", "A" },
- new int[] { 10, 20, 30, 40 });
- }
-
- public void testStripNamesFull() throws Exception {
- doTestStripNames(
- new int[] { 10, 10, 10, 10 },
- new String[] { "A", "B", "C", "D" },
- new int[] { 10 });
- }
-
- public void testStripNamesComplex() throws Exception {
- doTestStripNames(
- new int[] { 10, 20, 20, 30, 40, 40 },
- new String[] { "A", "A", "B", "A", "A", "B" },
- new int[] { 10, 20, 30, 40 });
- }
}
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
index a820ac0..676465e 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -617,6 +617,38 @@
looperThread.join();
}
+ public void testOpenProxyFileDescriptor_largeFile() throws Exception {
+ final ProxyFileDescriptorCallback callback = new ProxyFileDescriptorCallback() {
+ @Override
+ public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+ for (int i = 0; i < size; i++) {
+ data[i] = 'L';
+ }
+ return size;
+ }
+
+ @Override
+ public long onGetSize() throws ErrnoException {
+ return 8L * 1024L * 1024L * 1024L; // 8GB
+ }
+
+ @Override
+ public void onRelease() {}
+ };
+ final byte[] bytes = new byte[128];
+ try (final ParcelFileDescriptor fd = mStorageManager.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_READ_ONLY, callback)) {
+ assertEquals(8L * 1024L * 1024L * 1024L, fd.getStatSize());
+
+ final int readBytes = Os.pread(
+ fd.getFileDescriptor(), bytes, 0, bytes.length, fd.getStatSize() - 64L);
+ assertEquals(64, readBytes);
+ for (int i = 0; i < 64; i++) {
+ assertEquals('L', bytes[i]);
+ }
+ }
+ }
+
private void assertStorageVolumesEquals(StorageVolume volume, StorageVolume clone)
throws Exception {
// Asserts equals() method.
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/Android.mk b/tests/tests/packageinstaller/adminpackageinstaller/Android.mk
index 0e24292..fd0c1da 100755
--- a/tests/tests/packageinstaller/adminpackageinstaller/Android.mk
+++ b/tests/tests/packageinstaller/adminpackageinstaller/Android.mk
@@ -29,8 +29,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ub-uiautomator \
android-support-test \
- android-support-v4 \
- legacy-android-test
+ android-support-v4
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SDK_VERSION := test_current
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
index 3f09b25..2e4a4fe 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Config for CTS Admin Package Installer test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/tests/packageinstaller/externalsources/AndroidTest.xml b/tests/tests/packageinstaller/externalsources/AndroidTest.xml
index 2d782d8..a39baab 100644
--- a/tests/tests/packageinstaller/externalsources/AndroidTest.xml
+++ b/tests/tests/packageinstaller/externalsources/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Config for CTS External Sources test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java b/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
index 921f5f1..06e858e 100644
--- a/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
+++ b/tests/tests/packageinstaller/externalsources/src/android/packageinstaller/externalsources/cts/ExternalSourcesTest.java
@@ -41,7 +41,6 @@
private PackageManager mPm;
private String mPackageName;
private UiDevice mUiDevice;
- private boolean mHasFeature;
@Before
public void setUp() throws Exception {
@@ -49,7 +48,6 @@
mPm = mContext.getPackageManager();
mPackageName = mContext.getPackageName();
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- mHasFeature = !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
}
private void setAppOpsMode(String mode) throws IOException {
@@ -86,9 +84,6 @@
@Test
public void testManageUnknownSourcesExists() {
- if (!mHasFeature) {
- return;
- }
Intent manageUnknownSources = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
ResolveInfo info = mPm.resolveActivity(manageUnknownSources, 0);
Assert.assertNotNull("No activity found for " + manageUnknownSources.getAction(), info);
diff --git a/tests/tests/permission/Android.mk b/tests/tests/permission/Android.mk
index b24774f..2fc171a 100644
--- a/tests/tests/permission/Android.mk
+++ b/tests/tests/permission/Android.mk
@@ -33,7 +33,7 @@
ctstestrunner \
guava \
android-ex-camera2 \
- legacy-android-test
+ compatibility-device-util
LOCAL_JNI_SHARED_LIBRARIES := libctspermission_jni libnativehelper_compat_libc++
@@ -44,6 +44,7 @@
# uncomment when b/13249777 is fixed
#LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.base
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index b99cbc2..4fbcc10 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Permission test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
index c29d5f5..e17d0de 100644
--- a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
+++ b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
@@ -16,59 +16,217 @@
package android.permission.cts;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_READ_SMS;
+
+import android.Manifest.permission;
import android.app.AppOpsManager;
import android.content.Context;
-import android.test.AndroidTestCase;
+import android.os.Process;
+import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.AttributeSet;
-import junit.framework.AssertionFailedError;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
+import com.android.compatibility.common.util.SystemUtil;
-public class AppOpsTest extends AndroidTestCase {
- static final Class<?>[] sSetModeSignature = new Class[] {
- Context.class, AttributeSet.class};
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+public class AppOpsTest extends InstrumentationTestCase {
private AppOpsManager mAppOps;
+ private Context mContext;
+ private String mOpPackageName;
+ private int mMyUid;
+
+ // These permissions and opStrs must map to the same op codes.
+ private static Map<String, String> permissionToOpStr = new HashMap<>();
+
+ static {
+ permissionToOpStr.put(permission.ACCESS_COARSE_LOCATION,
+ AppOpsManager.OPSTR_COARSE_LOCATION);
+ permissionToOpStr.put(permission.ACCESS_FINE_LOCATION, AppOpsManager.OPSTR_FINE_LOCATION);
+ permissionToOpStr.put(permission.READ_CONTACTS, AppOpsManager.OPSTR_READ_CONTACTS);
+ permissionToOpStr.put(permission.WRITE_CONTACTS, AppOpsManager.OPSTR_WRITE_CONTACTS);
+ permissionToOpStr.put(permission.READ_CALL_LOG, AppOpsManager.OPSTR_READ_CALL_LOG);
+ permissionToOpStr.put(permission.WRITE_CALL_LOG, AppOpsManager.OPSTR_WRITE_CALL_LOG);
+ permissionToOpStr.put(permission.READ_CALENDAR, AppOpsManager.OPSTR_READ_CALENDAR);
+ permissionToOpStr.put(permission.WRITE_CALENDAR, AppOpsManager.OPSTR_WRITE_CALENDAR);
+ permissionToOpStr.put(permission.CALL_PHONE, AppOpsManager.OPSTR_CALL_PHONE);
+ permissionToOpStr.put(permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS);
+ permissionToOpStr.put(permission.RECEIVE_SMS, AppOpsManager.OPSTR_RECEIVE_SMS);
+ permissionToOpStr.put(permission.RECEIVE_MMS, AppOpsManager.OPSTR_RECEIVE_MMS);
+ permissionToOpStr.put(permission.RECEIVE_WAP_PUSH, AppOpsManager.OPSTR_RECEIVE_WAP_PUSH);
+ permissionToOpStr.put(permission.SEND_SMS, AppOpsManager.OPSTR_SEND_SMS);
+ permissionToOpStr.put(permission.READ_SMS, AppOpsManager.OPSTR_READ_SMS);
+ permissionToOpStr.put(permission.WRITE_SETTINGS, AppOpsManager.OPSTR_WRITE_SETTINGS);
+ permissionToOpStr.put(permission.SYSTEM_ALERT_WINDOW,
+ AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
+ permissionToOpStr.put(permission.ACCESS_NOTIFICATIONS,
+ AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+ permissionToOpStr.put(permission.CAMERA, AppOpsManager.OPSTR_CAMERA);
+ permissionToOpStr.put(permission.RECORD_AUDIO, AppOpsManager.OPSTR_RECORD_AUDIO);
+ permissionToOpStr.put(permission.READ_PHONE_STATE, AppOpsManager.OPSTR_READ_PHONE_STATE);
+ permissionToOpStr.put(permission.ADD_VOICEMAIL, AppOpsManager.OPSTR_ADD_VOICEMAIL);
+ permissionToOpStr.put(permission.USE_SIP, AppOpsManager.OPSTR_USE_SIP);
+ permissionToOpStr.put(permission.PROCESS_OUTGOING_CALLS,
+ AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS);
+ permissionToOpStr.put(permission.BODY_SENSORS, AppOpsManager.OPSTR_BODY_SENSORS);
+ permissionToOpStr.put(permission.READ_CELL_BROADCASTS,
+ AppOpsManager.OPSTR_READ_CELL_BROADCASTS);
+ permissionToOpStr.put(permission.READ_EXTERNAL_STORAGE,
+ AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE);
+ permissionToOpStr.put(permission.WRITE_EXTERNAL_STORAGE,
+ AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE);
+ }
@Override
protected void setUp() throws Exception {
super.setUp();
- mAppOps = (AppOpsManager)getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mContext = getInstrumentation().getContext();
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mOpPackageName = mContext.getOpPackageName();
+ mMyUid = Process.myUid();
assertNotNull(mAppOps);
}
+ public void testNoteOpAndCheckOp() throws Exception {
+ setAppOpMode(OPSTR_READ_SMS, MODE_ALLOWED);
+ assertEquals(MODE_ALLOWED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_ALLOWED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_ALLOWED, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_ALLOWED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+ setAppOpMode(OPSTR_READ_SMS, MODE_IGNORED);
+ assertEquals(MODE_IGNORED, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_IGNORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_IGNORED, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_IGNORED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+ setAppOpMode(OPSTR_READ_SMS, MODE_DEFAULT);
+ assertEquals(MODE_DEFAULT, mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_DEFAULT, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_DEFAULT, mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_DEFAULT, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+
+ setAppOpMode(OPSTR_READ_SMS, MODE_ERRORED);
+ assertEquals(MODE_ERRORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ assertEquals(MODE_ERRORED, mAppOps.checkOpNoThrow(OPSTR_READ_SMS, mMyUid, mOpPackageName));
+ try {
+ mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+ fail("SecurityException expected");
+ } catch (SecurityException expected) {
+ }
+ try {
+ mAppOps.checkOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+ fail("SecurityException expected");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ public void testCheckPackagePassesTest() throws Exception {
+ mAppOps.checkPackage(mMyUid, mOpPackageName);
+ mAppOps.checkPackage(Process.SYSTEM_UID, "android");
+ }
+
+ public void testCheckPackageDoesntPassTest() throws Exception {
+ try {
+ // Package name doesn't match UID.
+ mAppOps.checkPackage(Process.SYSTEM_UID, mOpPackageName);
+ fail("SecurityException expected");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ // Package name doesn't match UID.
+ mAppOps.checkPackage(mMyUid, "android");
+ fail("SecurityException expected");
+ } catch (SecurityException expected) {
+ }
+
+ try {
+ // Package name missing
+ mAppOps.checkPackage(mMyUid, "");
+ fail("SecurityException expected");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @SmallTest
+ public void testAllOpsHaveOpString() {
+ Set<String> opStrs = new HashSet<>();
+ for (String opStr : AppOpsManager.getOpStrs()) {
+ assertNotNull("Each app op must have an operation string defined", opStr);
+ opStrs.add(opStr);
+ }
+ assertEquals("Not all op strings are unique", AppOpsManager._NUM_OP, opStrs.size());
+ }
+
+ @SmallTest
+ public void testOpCodesUnique() {
+ String[] opStrs = AppOpsManager.getOpStrs();
+ Set<Integer> opCodes = new HashSet<>();
+ for (String opStr : opStrs) {
+ opCodes.add(AppOpsManager.strOpToOp(opStr));
+ }
+ assertEquals("Not all app op codes are unique", opStrs.length, opCodes.size());
+ }
+
+ @SmallTest
+ public void testPermissionMapping() {
+ for (String permission : permissionToOpStr.keySet()) {
+ testPermissionMapping(permission, permissionToOpStr.get(permission));
+ }
+ }
+
+ private void testPermissionMapping(String permission, String opStr) {
+ // Do the public value => internal op code lookups.
+ String mappedOpStr = AppOpsManager.permissionToOp(permission);
+ assertEquals(mappedOpStr, opStr);
+ int mappedOpCode = AppOpsManager.permissionToOpCode(permission);
+ int mappedOpCode2 = AppOpsManager.strOpToOp(opStr);
+ assertEquals(mappedOpCode, mappedOpCode2);
+
+ // Do the internal op code => public value lookup (reverse lookup).
+ String permissionMappedBack = AppOpsManager.opToPermission(mappedOpCode);
+ assertEquals(permission, permissionMappedBack);
+ }
+
/**
* Test that the app can not change the app op mode for itself.
*/
@SmallTest
- public void testSetMode() {
- boolean gotToTest = false;
+ public void testCantSetModeForSelf() {
try {
- Method setMode = mAppOps.getClass().getMethod("setMode", int.class, int.class,
- String.class, int.class);
- int writeSmsOp = mAppOps.getClass().getField("OP_WRITE_SMS").getInt(mAppOps);
- gotToTest = true;
- setMode.invoke(mAppOps, writeSmsOp, android.os.Process.myUid(),
- getContext().getPackageName(), AppOpsManager.MODE_ALLOWED);
+ int writeSmsOp = AppOpsManager.permissionToOpCode("android.permission.WRITE_SMS");
+ mAppOps.setMode(writeSmsOp, mMyUid, mOpPackageName, AppOpsManager.MODE_ALLOWED);
fail("Was able to set mode for self");
- } catch (NoSuchFieldException e) {
- throw new AssertionError("Unable to find OP_WRITE_SMS", e);
- } catch (NoSuchMethodException e) {
- throw new AssertionError("Unable to find setMode method", e);
- } catch (InvocationTargetException e) {
- if (!gotToTest) {
- throw new AssertionError("Whoops", e);
- }
- // If we got to the test, we want it to have thrown a security exception.
- // We need to look inside of the wrapper exception to see.
- Throwable t = e.getCause();
- if (!(t instanceof SecurityException)) {
- throw new AssertionError("Did not throw SecurityException", e);
- }
- } catch (IllegalAccessException e) {
- throw new AssertionError("Whoops", e);
+ } catch (SecurityException expected) {
}
}
+
+ private void setAppOpMode(String opStr, int mode) throws Exception {
+ String modeStr;
+ switch (mode) {
+ case MODE_ALLOWED:
+ modeStr = "allow";
+ break;
+ case MODE_ERRORED:
+ modeStr = "deny";
+ break;
+ case MODE_IGNORED:
+ modeStr = "ignore";
+ break;
+ case MODE_DEFAULT:
+ modeStr = "default";
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected app op type");
+ }
+ String command = "appops set " + mOpPackageName + " " + opStr + " " + modeStr;
+ SystemUtil.runShellCommand(getInstrumentation(), command);
+ }
}
diff --git a/tests/tests/permission2/Android.mk b/tests/tests/permission2/Android.mk
index 062aeb7..d1190a3 100755
--- a/tests/tests/permission2/Android.mk
+++ b/tests/tests/permission2/Android.mk
@@ -24,12 +24,11 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_JAVA_LIBRARIES := telephony-common android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index 2021b88..d4fb85f 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Permission test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index a4dad83..c72c8c1 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -183,15 +183,23 @@
<protected-broadcast
android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" />
+ <protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" />
+ <protected-broadcast
android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
+ android:name="android.bluetooth.input.profile.action.IDLE_TIME_CHANGED" />
+ <protected-broadcast
android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
<protected-broadcast
- android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" />
+ android:name="android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast
android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
@@ -299,6 +307,8 @@
<protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
<protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
+ <protected-broadcast android:name="com.android.server.stats.action.TRIGGER_COLLECTION" />
+
<protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
<protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
<protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
@@ -309,11 +319,16 @@
<protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" />
<protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
<protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE" />
<protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
<protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
<protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
@@ -392,6 +407,7 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
<protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
+ <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
<!-- Added in N -->
<protected-broadcast android:name="android.intent.action.ANR" />
@@ -470,7 +486,6 @@
<protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
<protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
- <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
@@ -489,6 +504,8 @@
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
<protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -547,6 +564,9 @@
<protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
<protected-broadcast android:name="com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
+ <!-- Made protected in P (was introduced in JB-MR2) -->
+ <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
@@ -563,6 +583,7 @@
android:icon="@drawable/perm_group_contacts"
android:label="@string/permgrouplab_contacts"
android:description="@string/permgroupdesc_contacts"
+ android:request="@string/permgrouprequest_contacts"
android:priority="100" />
<!-- Allows an application to read the user's contacts data.
@@ -593,6 +614,7 @@
android:icon="@drawable/perm_group_calendar"
android:label="@string/permgrouplab_calendar"
android:description="@string/permgroupdesc_calendar"
+ android:request="@string/permgrouprequest_calendar"
android:priority="200" />
<!-- Allows an application to read the user's calendar data.
@@ -623,6 +645,7 @@
android:icon="@drawable/perm_group_sms"
android:label="@string/permgrouplab_sms"
android:description="@string/permgroupdesc_sms"
+ android:request="@string/permgrouprequest_sms"
android:priority="300" />
<!-- Allows an application to send SMS messages.
@@ -699,6 +722,7 @@
android:icon="@drawable/perm_group_storage"
android:label="@string/permgrouplab_storage"
android:description="@string/permgroupdesc_storage"
+ android:request="@string/permgrouprequest_storage"
android:priority="900" />
<!-- Allows an application to read from external storage.
@@ -760,6 +784,7 @@
android:icon="@drawable/perm_group_location"
android:label="@string/permgrouplab_location"
android:description="@string/permgroupdesc_location"
+ android:request="@string/permgrouprequest_location"
android:priority="400" />
<!-- Allows an app to access precise location.
@@ -792,6 +817,7 @@
android:icon="@drawable/perm_group_phone_calls"
android:label="@string/permgrouplab_phone"
android:description="@string/permgroupdesc_phone"
+ android:request="@string/permgrouprequest_phone"
android:priority="500" />
<!-- Allows read only access to phone state, including the phone number of the device,
@@ -943,6 +969,7 @@
android:icon="@drawable/perm_group_microphone"
android:label="@string/permgrouplab_microphone"
android:description="@string/permgroupdesc_microphone"
+ android:request="@string/permgrouprequest_microphone"
android:priority="600" />
<!-- Allows an application to record audio.
@@ -985,6 +1012,7 @@
android:icon="@drawable/perm_group_camera"
android:label="@string/permgrouplab_camera"
android:description="@string/permgroupdesc_camera"
+ android:request="@string/permgrouprequest_camera"
android:priority="700" />
<!-- Required to be able to access the camera device.
@@ -1009,11 +1037,12 @@
<eat-comment />
<!-- Used for permissions that are associated with accessing
- camera or capturing images/video from the device. -->
+ body or environmental sensors. -->
<permission-group android:name="android.permission-group.SENSORS"
android:icon="@drawable/perm_group_sensors"
android:label="@string/permgrouplab_sensors"
android:description="@string/permgroupdesc_sensors"
+ android:request="@string/permgrouprequest_sensors"
android:priority="800" />
<!-- Allows an application to access data from sensors that the user uses to
@@ -1591,6 +1620,11 @@
<permission android:name="android.permission.ACCESS_PDB_STATE"
android:protectionLevel="signature" />
+ <!-- Allows testing if a passwords is forbidden by the admins.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows system update service to notify device owner about pending updates.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
@@ -1712,7 +1746,7 @@
@hide
-->
<permission android:name="android.permission.BIND_IMS_SERVICE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
<!-- Allows an application to manage embedded subscriptions (those on a eUICC) through
EuiccManager APIs.
@@ -1763,6 +1797,15 @@
<permission android:name="android.permission.ALLOCATE_AGGRESSIVE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide
+ Allows an application to use reserved disk space.
+ <p>Not for use by third-party applications. Should only be requested by
+ apps that provide core system functionality, to ensure system stability
+ when disk is otherwise completely full.
+ -->
+ <permission android:name="android.permission.USE_RESERVED_DISK"
+ android:protectionLevel="signature|privileged" />
+
<!-- ================================== -->
<!-- Permissions for screenlock -->
<!-- ================================== -->
@@ -1860,11 +1903,11 @@
<!-- @SystemApi @hide Allows an application to create/manage/remove stacks -->
<permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|development" />
<!-- @SystemApi @hide Allows an application to embed other activities -->
<permission android:name="android.permission.ACTIVITY_EMBEDDING"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|development" />
<!-- Allows an application to start any activity, regardless of permission
protection or exported state.
@@ -2030,6 +2073,11 @@
<eat-comment />
<!-- Allows an application to install a shortcut in Launcher.
+ <p>In Android O (API level 26) and higher, the <code>INSTALL_SHORTCUT</code> broadcast no
+ longer has any effect on your app because it's a private, implicit
+ broadcast. Instead, you should create an app shortcut by using the
+ {@link android.content.pm.ShortcutManager#requestPinShortcut requestPinShortcut()}
+ method from the {@link android.content.pm.ShortcutManager} class.
<p>Protection level: normal
-->
<permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
@@ -2270,7 +2318,7 @@
<p>An application requesting this permission is responsible for
verifying the source and integrity of the update before passing
it off to the installer components.
- @hide -->
+ @SystemApi @hide -->
<permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
android:protectionLevel="signature|privileged" />
@@ -2390,7 +2438,8 @@
<permission android:name="android.permission.UPDATE_DEVICE_STATS"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi @hide Allows an application to collect battery statistics -->
+ <!-- @SystemApi @hide Allows an application to collect application operation statistics.
+ Not for use by third party apps. -->
<permission android:name="android.permission.GET_APP_OPS_STATS"
android:protectionLevel="signature|privileged|development" />
@@ -2415,10 +2464,10 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to use
- {@link android.view.WindowManager.LayoutsParams#PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
- to hide non-system-overlay windows.
- <p>Not for use by third-party applications.
- @hide
+ {@link android.view.WindowManager.LayoutsParams#PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
+ to hide non-system-overlay windows.
+ <p>Not for use by third-party applications.
+ @hide
-->
<permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
android:protectionLevel="signature|installer" />
@@ -2625,10 +2674,6 @@
<permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
android:protectionLevel="signature" />
- <!-- @hide TODO(b/37563972): remove once clients use BIND_AUTOFILL_SERVICE -->
- <permission android:name="android.permission.BIND_AUTOFILL"
- android:protectionLevel="signature" />
-
<!-- Must be required by hotword enrollment application,
to ensure that only the system can interact with it.
@hide <p>Not for use by third-party applications.</p> -->
@@ -2754,8 +2799,9 @@
android:protectionLevel="signature|appop" />
<!-- Allows an application to request deleting packages. Apps
- targeting APIs greater than 25 must hold this permission in
- order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE}.
+ targeting APIs {@link android.os.Build.VERSION_CODES#P} or greater must hold this
+ permission in order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE} or
+ {@link android.content.pm.PackageInstaller#uninstall}.
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_DELETE_PACKAGES"
@@ -2766,6 +2812,14 @@
<!-- @SystemApi Allows an application to install packages.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.INSTALL_PACKAGES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to install self updates. This is a limited version
+ of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.INSTALL_SELF_UPDATES"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to clear user data.
@@ -2877,6 +2931,18 @@
<permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
android:protectionLevel="signature" />
+ <!-- Allows an application to collect usage infomation about brightness slider changes.
+ <p>Not for use by third-party applications.</p>
+ TODO: make a System API
+ @hide -->
+ <permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to modify the display brightness configuration
+ @hide -->
+ <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @SystemApi Allows an application to control VPN.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -3032,10 +3098,10 @@
android:protectionLevel="signature|privileged|development|appop" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <!-- @hide Allows an application to change the app idle state of an app.
+ <!-- @hide @SystemApi Allows an application to change the app idle state of an app.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
access the network and acquire wakelocks.
@@ -3056,6 +3122,12 @@
<permission android:name="android.permission.BATTERY_STATS"
android:protectionLevel="signature|privileged|development" />
+ <!--Allows an application to manage statscompanion.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.STATSCOMPANION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to control the backup and restore process.
<p>Not for use by third-party applications.
@hide pending API council -->
@@ -3082,6 +3154,13 @@
<permission android:name="android.permission.BIND_APPWIDGET"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to bind app's slices and get their
+ content. This content will be surfaced to the
+ user and not to leave the device.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BIND_SLICE"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @SystemApi Private permission, to restrict who can bring up a dialog to add a new
keyguard widget
@hide -->
@@ -3126,6 +3205,11 @@
<permission android:name="android.permission.READ_SEARCH_INDEXABLES"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Internal permission to allows an application to bind to suggestion service.
+ @hide -->
+ <permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows applications to set a live wallpaper.
@hide XXX Change to signature once the picker is moved to its
own apk as Ghod Intended. -->
@@ -3430,6 +3514,7 @@
@hide -->
<permission android:name="android.permission.LOCAL_MAC_ADDRESS"
android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"/>
<!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
@hide -->
@@ -3458,11 +3543,19 @@
@hide -->
<permission android:name="android.permission.ACCESS_INSTANT_APPS"
android:protectionLevel="signature|installer|verifier" />
+ <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
<!-- Allows the holder to view the instant applications on the device.
@hide -->
<permission android:name="android.permission.VIEW_INSTANT_APPS"
- android:protectionLevel="signature|preinstalled" />
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Allows the holder to manage whether the system can bind to services
+ provided by instant apps. This permission is intended to protect
+ test/development fucntionality and should be used only in such cases.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"
+ android:protectionLevel="signature" />
<!-- Allows receiving the usage of media resource e.g. video/audio codec and
graphic memory.
@@ -3548,6 +3641,20 @@
<permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
android:protectionLevel="signature|development|instant|appop" />
+ <!-- @hide Allows system components to access all app shortcuts. -->
+ <permission android:name="android.permission.ACCESS_SHORTCUTS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to read the runtime profiles of other apps.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_RUNTIME_PROFILES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to turn on / off quiet mode.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MODIFY_QUIET_MODE"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
@@ -3605,7 +3712,7 @@
</activity-alias>
<activity-alias android:name="com.android.internal.app.ForwardIntentToManagedProfile"
android:targetActivity="com.android.internal.app.IntentForwarderActivity"
- android:icon="@drawable/ic_corp_icon"
+ android:icon="@drawable/ic_corp_badge"
android:exported="true"
android:label="@string/managed_profile_label">
</activity-alias>
@@ -3772,6 +3879,14 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
@@ -3788,14 +3903,6 @@
</intent-filter>
</receiver>
- <receiver android:name="com.android.server.updates.TzDataInstallReceiver"
- android:permission="android.permission.UPDATE_CONFIG">
- <intent-filter>
- <action android:name="android.intent.action.UPDATE_TZDATA" />
- <data android:scheme="content" android:host="*" android:mimeType="*/*" />
- </intent-filter>
- </receiver>
-
<receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
android:permission="android.permission.UPDATE_CONFIG">
<intent-filter>
@@ -3836,6 +3943,16 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.stats.StatsCompanionService$AnomalyAlarmReceiver"
+ android:permission="android.permission.STATSCOMPANION"
+ android:exported="false">
+ </receiver>
+
+ <receiver android:name="com.android.server.stats.StatsCompanionService$PollingAlarmReceiver"
+ android:permission="android.permission.STATSCOMPANION"
+ android:exported="false">
+ </receiver>
+
<service android:name="android.hardware.location.GeofenceHardwareService"
android:permission="android.permission.LOCATION_HARDWARE"
android:exported="false" />
@@ -3887,6 +4004,17 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- </application>
+ <service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.display.BrightnessIdleJob"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+</application>
</manifest>
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
new file mode 100644
index 0000000..4a68183
--- /dev/null
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.car"
+ coreApp="true"
+ android:sharedUserId="android.uid.system">
+
+ <original-package android:name="com.android.car" />
+
+ <permission-group
+ android:name="android.car.permission-group.CAR_INFORMATION"
+ android:icon="@drawable/car_ic_mode"
+ android:description="@string/car_permission_desc"
+ android:label="@string/car_permission_label" />
+ <permission
+ android:name="android.car.permission.CAR_CABIN"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_cabin"
+ android:description="@string/car_permission_desc_cabin" />
+ <permission
+ android:name="android.car.permission.CAR_CAMERA"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_camera"
+ android:description="@string/car_permission_desc_camera" />
+ <permission
+ android:name="android.car.permission.CAR_FUEL"
+ android:permissionGroup="android.car.permission-group.CAR_INFORMATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_fuel"
+ android:description="@string/car_permission_desc_fuel" />
+ <permission
+ android:name="android.car.permission.CAR_HVAC"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_hvac"
+ android:description="@string/car_permission_desc_hvac" />
+ <permission
+ android:name="android.car.permission.CAR_MILEAGE"
+ android:permissionGroup="android.car.permission-group.CAR_INFORMATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_mileage"
+ android:description="@string/car_permission_desc_mileage" />
+ <permission
+ android:name="android.car.permission.CAR_SPEED"
+ android:permissionGroup="android.permission-group.LOCATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_speed"
+ android:description="@string/car_permission_desc_speed" />
+ <permission
+ android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"
+ android:permissionGroup="android.car.permission-group.CAR_INFORMATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_vehicle_dynamics_state"
+ android:description="@string/car_permission_desc_vehicle_dynamics_state" />
+ <permission
+ android:name="android.car.permission.CAR_VENDOR_EXTENSION"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_vendor_extension"
+ android:description="@string/car_permission_desc_vendor_extension" />
+ <permission
+ android:name="android.car.permission.CAR_RADIO"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_radio"
+ android:description="@string/car_permission_desc_radio" />
+ <permission
+ android:name="android.car.permission.CAR_PROJECTION"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_projection"
+ android:description="@string/car_permission_desc_projection" />
+ <permission
+ android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_mock_vehicle_hal"
+ android:description="@string/car_permission_desc_mock_vehicle_hal" />
+ <permission
+ android:name="android.car.permission.CAR_NAVIGATION_MANAGER"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_car_navigation_manager"
+ android:description="@string/car_permission_desc_car_navigation_manager" />
+ <permission
+ android:name="android.car.permission.DIAGNOSTIC_READ_ALL"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_diag_read"
+ android:description="@string/car_permission_desc_diag_read" />
+ <permission
+ android:name="android.car.permission.DIAGNOSTIC_CLEAR"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_diag_clear"
+ android:description="@string/car_permission_desc_diag_clear" />
+ <permission
+ android:name="android.car.permission.VMS_PUBLISHER"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_vms_publisher"
+ android:description="@string/car_permission_desc_vms_publisher" />
+ <permission
+ android:name="android.car.permission.VMS_SUBSCRIBER"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_vms_subscriber"
+ android:description="@string/car_permission_desc_vms_subscriber" />
+
+ <!-- may replace this with system permission if proper one is defined. -->
+ <permission
+ android:name="android.car.permission.CONTROL_APP_BLOCKING"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_control_app_blocking"
+ android:description="@string/car_permission_desc_control_app_blocking" />
+
+ <permission
+ android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_audio_volume"
+ android:description="@string/car_permission_desc_audio_volume" />
+
+ <permission
+ android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_audio_settings"
+ android:description="@string/car_permission_desc_audio_settings" />
+
+ <permission
+ android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
+ android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
+
+ <permission
+ android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_input_service"
+ android:description="@string/car_permission_desc_bind_input_service"/>
+
+ <permission
+ android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_car_display_in_cluster"
+ android:description="@string/car_permission_desc_car_display_in_cluster" />
+
+ <permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_car_cluster_control"
+ android:description="@string/car_permission_desc_car_cluster_control" />
+
+ <permission android:name="android.car.permission.STORAGE_MONITORING"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_storage_monitoring"
+ android:description="@string/car_permission_desc_storage_monitoring" />
+
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+ <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+ <uses-permission android:name="android.permission.REBOOT" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.REMOVE_TASKS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+ <application android:label="Car service"
+ android:directBootAware="true"
+ android:allowBackup="false"
+ android:persistent="true">
+
+
+ <uses-library android:name="android.test.runner" />
+ <service android:name=".CarService"
+ android:singleUser="true">
+ <intent-filter>
+ <action android:name="android.car.ICar" />
+ </intent-filter>
+ </service>
+ <service android:name=".PerUserCarService" android:exported="false" />
+ <activity android:name="com.android.car.pm.ActivityBlockingActivity"
+ android:excludeFromRecents="true"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 35a99da..68e29a6 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -16,8 +16,12 @@
package android.permission2.cts;
+import static android.os.Build.VERSION.SECURITY_PATCH;
+
+import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.test.AndroidTestCase;
@@ -26,10 +30,10 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Xml;
+
import org.xmlpull.v1.XmlPullParser;
import java.io.InputStream;
-import java.lang.String;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -38,8 +42,6 @@
import java.util.Map;
import java.util.Set;
-import static android.os.Build.VERSION.SECURITY_PATCH;
-
/**
* Tests for permission policy on the platform.
*/
@@ -54,6 +56,8 @@
private static final String PLATFORM_ROOT_NAMESPACE = "android.";
+ private static final String AUTOMOTIVE_SERVICE_PACKAGE_NAME = "com.android.car";
+
private static final String TAG_PERMISSION = "permission";
private static final String ATTR_NAME = "name";
@@ -61,14 +65,10 @@
private static final String ATTR_PROTECTION_LEVEL = "protectionLevel";
public void testPlatformPermissionPolicyUnaltered() throws Exception {
- PackageInfo platformPackage = getContext().getPackageManager()
- .getPackageInfo(PLATFORM_PACKAGE_NAME, PackageManager.GET_PERMISSIONS);
- Map<String, PermissionInfo> declaredPermissionsMap = new ArrayMap<>();
- List<String> offendingList = new ArrayList<String>();
+ Map<String, PermissionInfo> declaredPermissionsMap =
+ getPermissionsForPackage(getContext(), PLATFORM_PACKAGE_NAME);
- for (PermissionInfo declaredPermission : platformPackage.permissions) {
- declaredPermissionsMap.put(declaredPermission.name, declaredPermission);
- }
+ List<String> offendingList = new ArrayList<>();
List<PermissionGroupInfo> declaredGroups = getContext().getPackageManager()
.getAllPermissionGroups(0);
@@ -77,9 +77,16 @@
declaredGroupsSet.add(declaredGroup.name);
}
- Set<String> expectedPermissionGroups = new ArraySet<String>();
+ Set<String> expectedPermissionGroups = new ArraySet<>();
+ List<PermissionInfo> expectedPermissions = loadExpectedPermissions(R.raw.android_manifest);
- for (PermissionInfo expectedPermission : loadExpectedPermissions()) {
+ if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ expectedPermissions.addAll(loadExpectedPermissions(R.raw.automotive_android_manifest));
+ declaredPermissionsMap.putAll(
+ getPermissionsForPackage(getContext(), AUTOMOTIVE_SERVICE_PACKAGE_NAME));
+ }
+
+ for (PermissionInfo expectedPermission : expectedPermissions) {
String expectedPermissionName = expectedPermission.name;
if (shouldSkipPermission(expectedPermissionName)) {
continue;
@@ -132,7 +139,7 @@
if (!declaredGroupsSet.contains(declaredPermission.group)) {
offendingList.add(
- "Permission group " + expectedPermission.group + "must be defined");
+ "Permission group " + expectedPermission.group + " must be defined");
}
}
}
@@ -140,7 +147,11 @@
// OEMs cannot define permissions in the platform namespace
for (String permission : declaredPermissionsMap.keySet()) {
if (permission.startsWith(PLATFORM_ROOT_NAMESPACE)) {
- offendingList.add("Cannot define permission in android namespace:" + permission);
+ final PermissionInfo permInfo = declaredPermissionsMap.get(permission);
+ offendingList.add(
+ "Cannot define permission " + permission
+ + ", package " + permInfo.packageName
+ + " in android namespace");
}
}
@@ -151,11 +162,9 @@
if (declaredGroup.packageName.equals(PLATFORM_PACKAGE_NAME)
|| declaredGroup.name.startsWith(PLATFORM_ROOT_NAMESPACE)) {
offendingList.add(
- "Cannot define group "
- + declaredGroup.name
- + ", package "
- + declaredGroup.packageName
- + " in android namespace");
+ "Cannot define group " + declaredGroup.name
+ + ", package " + declaredGroup.packageName
+ + " in android namespace");
}
}
}
@@ -177,12 +186,9 @@
assertTrue(errMsg, offendingList.isEmpty());
}
- private List<PermissionInfo> loadExpectedPermissions() throws Exception {
+ private List<PermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
List<PermissionInfo> permissions = new ArrayList<>();
- try (
- InputStream in = getContext().getResources()
- .openRawResource(android.permission2.cts.R.raw.android_manifest)
- ) {
+ try (InputStream in = getContext().getResources().openRawResource(resourceId)) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
@@ -250,6 +256,9 @@
case "privileged": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRIVILEGED;
} break;
+ case "vendorPrivileged": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED;
+ } break;
case "setup": {
protectionLevel |= PermissionInfo.PROTECTION_FLAG_SETUP;
} break;
@@ -264,6 +273,18 @@
return protectionLevel;
}
+ private static Map<String, PermissionInfo> getPermissionsForPackage(Context context, String pkg)
+ throws NameNotFoundException {
+ PackageInfo packageInfo = context.getPackageManager()
+ .getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+ Map<String, PermissionInfo> declaredPermissionsMap = new ArrayMap<>();
+
+ for (PermissionInfo declaredPermission : packageInfo.permissions) {
+ declaredPermissionsMap.put(declaredPermission.name, declaredPermission);
+ }
+ return declaredPermissionsMap;
+ }
+
private static Date parseDate(String date) {
Date patchDate = new Date();
try {
diff --git a/tests/tests/preference/Android.mk b/tests/tests/preference/Android.mk
index 9223ce8..dd0ebf7 100644
--- a/tests/tests/preference/Android.mk
+++ b/tests/tests/preference/Android.mk
@@ -23,7 +23,9 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/preference/AndroidTest.xml b/tests/tests/preference/AndroidTest.xml
index f26d282..584bbc8 100644
--- a/tests/tests/preference/AndroidTest.xml
+++ b/tests/tests/preference/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Preference test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/preference2/Android.mk b/tests/tests/preference2/Android.mk
index 7529f43..8f1c3a2 100644
--- a/tests/tests/preference2/Android.mk
+++ b/tests/tests/preference2/Android.mk
@@ -29,8 +29,9 @@
ctstestrunner \
compatibility-device-util \
mockito-target-minus-junit4 \
- ub-uiautomator \
- legacy-android-test
+ ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/preference2/AndroidTest.xml b/tests/tests/preference2/AndroidTest.xml
index 691c45b3..b13eba5 100644
--- a/tests/tests/preference2/AndroidTest.xml
+++ b/tests/tests/preference2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Preference test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/print/Android.mk b/tests/tests/print/Android.mk
index c9b036a..aba1b80 100644
--- a/tests/tests/print/Android.mk
+++ b/tests/tests/print/Android.mk
@@ -16,7 +16,7 @@
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
@@ -27,8 +27,9 @@
LOCAL_PACKAGE_NAME := CtsPrintTestCases
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target-minus-junit4 ctstestrunner ub-uiautomator compatibility-device-util android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := print-test-util-lib
LOCAL_SDK_VERSION := test_current
include $(BUILD_CTS_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/tests/tests/print/AndroidManifest.xml b/tests/tests/print/AndroidManifest.xml
index c5148c8..ef86c30 100644
--- a/tests/tests/print/AndroidManifest.xml
+++ b/tests/tests/print/AndroidManifest.xml
@@ -25,10 +25,12 @@
<uses-library android:name="android.test.runner"/>
- <activity android:name="android.print.cts.PrintDocumentActivity"/>
+ <activity
+ android:name="android.print.test.PrintDocumentActivity"
+ android:theme="@style/NoAnimation" />
<service
- android:name="android.print.cts.services.FirstPrintService"
+ android:name="android.print.test.services.FirstPrintService"
android:permission="android.permission.BIND_PRINT_SERVICE">
<intent-filter>
<action android:name="android.printservice.PrintService" />
@@ -40,7 +42,7 @@
</service>
<service
- android:name="android.print.cts.services.SecondPrintService"
+ android:name="android.print.test.services.SecondPrintService"
android:permission="android.permission.BIND_PRINT_SERVICE">
<intent-filter>
<action android:name="android.printservice.PrintService" />
@@ -52,25 +54,28 @@
</service>
<activity
- android:name="android.print.cts.services.SettingsActivity"
+ android:name="android.print.test.services.SettingsActivity"
+ android:theme="@style/NoAnimation"
android:exported="true">
</activity>
<activity
- android:name="android.print.cts.services.AddPrintersActivity"
+ android:name="android.print.test.services.AddPrintersActivity"
+ android:theme="@style/NoAnimation"
android:exported="true">
</activity>
<activity
- android:name="android.print.cts.services.InfoActivity"
- android:exported="true">
+ android:name="android.print.test.services.InfoActivity"
+ android:theme="@style/NoAnimation"
+ android:exported="true">
</activity>
<activity
- android:name="android.print.cts.services.CustomPrintOptionsActivity"
+ android:name="android.print.test.services.CustomPrintOptionsActivity"
android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
android:exported="true"
- android:theme="@style/Theme.Translucent">
+ android:theme="@style/NoAnimationTranslucent">
</activity>
</application>
diff --git a/tests/tests/print/AndroidTest.xml b/tests/tests/print/AndroidTest.xml
index 1c771d6..e8eb8cd 100644
--- a/tests/tests/print/AndroidTest.xml
+++ b/tests/tests/print/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Print test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="component" value="framework" />
diff --git a/tests/tests/print/printTestUtilLib/Android.mk b/tests/tests/print/printTestUtilLib/Android.mk
new file mode 100644
index 0000000..9b6086a
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017s The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := print-test-util-lib
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target-minus-junit4 ctstestrunner ub-uiautomator compatibility-device-util android-support-test
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
new file mode 100644
index 0000000..fbfff33
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
@@ -0,0 +1,1150 @@
+/*
+ * 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.print.test;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.GET_SERVICES;
+import static android.print.test.Utils.getPrintManager;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.pdf.PdfDocument;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.pdf.PrintedPdfDocument;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.StubbablePrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
+import android.printservice.CustomPrinterIconCallback;
+import android.printservice.PrintJob;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+import org.mockito.InOrder;
+import org.mockito.stubbing.Answer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This is the base class for print tests.
+ */
+public abstract class BasePrintTest {
+ private static final String LOG_TAG = "BasePrintTest";
+
+ protected static final long OPERATION_TIMEOUT_MILLIS = 60000;
+ protected static final String PRINT_JOB_NAME = "Test";
+ static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID";
+
+ private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
+ private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
+ private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
+ private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
+ private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
+ private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
+ private static final String PRINTSPOOLER_PACKAGE = "com.android.printspooler";
+
+ private static final AtomicInteger sLastTestID = new AtomicInteger();
+ private int mTestId;
+ private PrintDocumentActivity mActivity;
+
+ private static String sDisabledPrintServicesBefore;
+
+ private static final SparseArray<BasePrintTest> sIdToTest = new SparseArray<>();
+
+ public final @Rule ShouldStartActivity mShouldStartActivityRule = new ShouldStartActivity();
+
+ /**
+ * Return the UI device
+ *
+ * @return the UI device
+ */
+ public UiDevice getUiDevice() {
+ return UiDevice.getInstance(getInstrumentation());
+ }
+
+ private CallCounter mCancelOperationCounter;
+ private CallCounter mLayoutCallCounter;
+ private CallCounter mWriteCallCounter;
+ private CallCounter mWriteCancelCallCounter;
+ private CallCounter mStartCallCounter;
+ private CallCounter mFinishCallCounter;
+ private CallCounter mPrintJobQueuedCallCounter;
+ private CallCounter mCreateSessionCallCounter;
+ private CallCounter mDestroySessionCallCounter;
+ private CallCounter mDestroyActivityCallCounter = new CallCounter();
+ private CallCounter mCreateActivityCallCounter = new CallCounter();
+
+ private static String[] sEnabledImes;
+
+ private static String[] getEnabledImes() throws IOException {
+ List<String> imeList = new ArrayList<>();
+
+ ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+ .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ imeList.add(line);
+ }
+ }
+
+ String[] imeArray = new String[imeList.size()];
+ imeList.toArray(imeArray);
+
+ return imeArray;
+ }
+
+ private static void disableImes() throws Exception {
+ sEnabledImes = getEnabledImes();
+ for (String ime : sEnabledImes) {
+ String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
+ SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
+ }
+ }
+
+ private static void enableImes() throws Exception {
+ for (String ime : sEnabledImes) {
+ String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
+ SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
+ }
+ sEnabledImes = null;
+ }
+
+ protected static Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ Log.d(LOG_TAG, "setUpClass()");
+
+ Instrumentation instrumentation = getInstrumentation();
+
+ // Make sure we start with a clean slate.
+ Log.d(LOG_TAG, "clearPrintSpoolerData()");
+ clearPrintSpoolerData();
+ Log.d(LOG_TAG, "disableImes()");
+ disableImes();
+ Log.d(LOG_TAG, "disablePrintServices()");
+ disablePrintServices(instrumentation.getTargetContext().getPackageName());
+
+ // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+ // Dexmaker is used by mockito.
+ System.setProperty("dexmaker.dexcache", instrumentation
+ .getTargetContext().getCacheDir().getPath());
+
+ Log.d(LOG_TAG, "setUpClass() done");
+ }
+
+ /**
+ * Disable all print services beside the ones we want to leave enabled.
+ *
+ * @param packageToLeaveEnabled The package of the services to leave enabled.
+ */
+ private static void disablePrintServices(@NonNull String packageToLeaveEnabled)
+ throws IOException {
+ Instrumentation instrumentation = getInstrumentation();
+
+ sDisabledPrintServicesBefore = SystemUtil.runShellCommand(instrumentation,
+ "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
+
+ Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
+ List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager()
+ .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA);
+
+ StringBuilder builder = new StringBuilder();
+ for (ResolveInfo service : installedServices) {
+ if (packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
+ continue;
+ }
+ if (builder.length() > 0) {
+ builder.append(":");
+ }
+ builder.append(new ComponentName(service.serviceInfo.packageName,
+ service.serviceInfo.name).flattenToString());
+ }
+
+ SystemUtil.runShellCommand(instrumentation, "settings put secure "
+ + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
+ }
+
+ /**
+ * Revert {@link #disablePrintServices(String)}
+ */
+ private static void enablePrintServices() throws IOException {
+ SystemUtil.runShellCommand(getInstrumentation(),
+ "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
+ + sDisabledPrintServicesBefore);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ Log.d(LOG_TAG, "setUp()");
+
+ assumeTrue(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_PRINTING));
+
+ // Initialize the latches.
+ Log.d(LOG_TAG, "init counters");
+ mCancelOperationCounter = new CallCounter();
+ mLayoutCallCounter = new CallCounter();
+ mStartCallCounter = new CallCounter();
+ mFinishCallCounter = new CallCounter();
+ mWriteCallCounter = new CallCounter();
+ mWriteCancelCallCounter = new CallCounter();
+ mFinishCallCounter = new CallCounter();
+ mPrintJobQueuedCallCounter = new CallCounter();
+ mCreateSessionCallCounter = new CallCounter();
+ mDestroySessionCallCounter = new CallCounter();
+
+ mTestId = sLastTestID.incrementAndGet();
+ sIdToTest.put(mTestId, this);
+
+ // Create the activity if needed
+ if (!mShouldStartActivityRule.mNoActivity) {
+ createActivity();
+ }
+
+ Log.d(LOG_TAG, "setUp() done");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Log.d(LOG_TAG, "tearDown()");
+
+ finishActivity();
+
+ sIdToTest.remove(mTestId);
+
+ Log.d(LOG_TAG, "tearDown() done");
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ Log.d(LOG_TAG, "tearDownClass()");
+
+ Instrumentation instrumentation = getInstrumentation();
+
+ Log.d(LOG_TAG, "enablePrintServices()");
+ enablePrintServices();
+
+ Log.d(LOG_TAG, "enableImes()");
+ enableImes();
+
+ // Make sure the spooler is cleaned, this also un-approves all services
+ Log.d(LOG_TAG, "clearPrintSpoolerData()");
+ clearPrintSpoolerData();
+
+ SystemUtil.runShellCommand(instrumentation, "settings put secure "
+ + Settings.Secure.DISABLED_PRINT_SERVICES + " null");
+
+ Log.d(LOG_TAG, "tearDownClass() done");
+ }
+
+ protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
+ final PrintAttributes attributes) {
+ android.print.PrintJob[] printJob = new android.print.PrintJob[1];
+ // Initiate printing as if coming from the app.
+ getInstrumentation().runOnMainSync(() -> {
+ PrintManager printManager = (PrintManager) getActivity()
+ .getSystemService(Context.PRINT_SERVICE);
+ printJob[0] = printManager.print("Print job", adapter, attributes);
+ });
+
+ return printJob[0];
+ }
+
+ protected void print(PrintDocumentAdapter adapter) {
+ print(adapter, (PrintAttributes) null);
+ }
+
+ protected void print(PrintDocumentAdapter adapter, String printJobName) {
+ print(adapter, printJobName, null);
+ }
+
+ /**
+ * Start printing
+ *
+ * @param adapter Adapter supplying data to print
+ * @param printJobName The name of the print job
+ */
+ protected void print(@NonNull PrintDocumentAdapter adapter, @NonNull String printJobName,
+ @Nullable PrintAttributes attributes) {
+ // Initiate printing as if coming from the app.
+ getInstrumentation()
+ .runOnMainSync(() -> getPrintManager(getActivity()).print(printJobName, adapter,
+ attributes));
+ }
+
+ protected void onCancelOperationCalled() {
+ mCancelOperationCounter.call();
+ }
+
+ public void onStartCalled() {
+ mStartCallCounter.call();
+ }
+
+ protected void onLayoutCalled() {
+ mLayoutCallCounter.call();
+ }
+
+ protected int getWriteCallCount() {
+ return mWriteCallCounter.getCallCount();
+ }
+
+ protected void onWriteCalled() {
+ mWriteCallCounter.call();
+ }
+
+ protected void onWriteCancelCalled() {
+ mWriteCancelCallCounter.call();
+ }
+
+ protected void onFinishCalled() {
+ mFinishCallCounter.call();
+ }
+
+ protected void onPrintJobQueuedCalled() {
+ mPrintJobQueuedCallCounter.call();
+ }
+
+ protected void onPrinterDiscoverySessionCreateCalled() {
+ mCreateSessionCallCounter.call();
+ }
+
+ protected void onPrinterDiscoverySessionDestroyCalled() {
+ mDestroySessionCallCounter.call();
+ }
+
+ protected void waitForCancelOperationCallbackCalled() {
+ waitForCallbackCallCount(mCancelOperationCounter, 1,
+ "Did not get expected call to onCancel for the current operation.");
+ }
+
+ protected void waitForPrinterDiscoverySessionCreateCallbackCalled() {
+ waitForCallbackCallCount(mCreateSessionCallCounter, 1,
+ "Did not get expected call to onCreatePrinterDiscoverySession.");
+ }
+
+ public void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
+ waitForCallbackCallCount(mDestroySessionCallCounter, count,
+ "Did not get expected call to onDestroyPrinterDiscoverySession.");
+ }
+
+ protected void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
+ waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
+ "Did not get expected call to onPrintJobQueued.");
+ }
+
+ protected void waitForAdapterStartCallbackCalled() {
+ waitForCallbackCallCount(mStartCallCounter, 1,
+ "Did not get expected call to start.");
+ }
+
+ protected void waitForAdapterFinishCallbackCalled() {
+ waitForCallbackCallCount(mFinishCallCounter, 1,
+ "Did not get expected call to finish.");
+ }
+
+ protected void waitForLayoutAdapterCallbackCount(int count) {
+ waitForCallbackCallCount(mLayoutCallCounter, count,
+ "Did not get expected call to layout.");
+ }
+
+ public void waitForWriteAdapterCallback(int count) {
+ waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
+ }
+
+ protected void waitForWriteCancelCallback(int count) {
+ waitForCallbackCallCount(mWriteCancelCallCounter, count,
+ "Did not get expected cancel of write.");
+ }
+
+ private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
+ try {
+ counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS);
+ } catch (TimeoutException te) {
+ fail(message);
+ }
+ }
+
+ /**
+ * Indicate the print activity was created.
+ */
+ static void onActivityCreateCalled(int testId, PrintDocumentActivity activity) {
+ synchronized (sIdToTest) {
+ BasePrintTest test = sIdToTest.get(testId);
+ if (test != null) {
+ test.mActivity = activity;
+ test.mCreateActivityCallCounter.call();
+ }
+ }
+ }
+
+ /**
+ * Indicate the print activity was destroyed.
+ */
+ static void onActivityDestroyCalled(int testId) {
+ synchronized (sIdToTest) {
+ BasePrintTest test = sIdToTest.get(testId);
+ if (test != null) {
+ test.mDestroyActivityCallCounter.call();
+ }
+ }
+ }
+
+ private void finishActivity() {
+ Activity activity = mActivity;
+
+ if (activity != null) {
+ if (!activity.isFinishing()) {
+ activity.finish();
+ }
+
+ while (!activity.isDestroyed()) {
+ int creates = mCreateActivityCallCounter.getCallCount();
+ waitForCallbackCallCount(mDestroyActivityCallCounter, creates,
+ "Activity was not destroyed");
+ }
+ }
+ }
+
+ /**
+ * Get the number of ties the print activity was destroyed.
+ *
+ * @return The number of onDestroy calls on the print activity.
+ */
+ protected int getActivityDestroyCallbackCallCount() {
+ return mDestroyActivityCallCounter.getCallCount();
+ }
+
+ /**
+ * Get the number of ties the print activity was created.
+ *
+ * @return The number of onCreate calls on the print activity.
+ */
+ private int getActivityCreateCallbackCallCount() {
+ return mCreateActivityCallCounter.getCallCount();
+ }
+
+ /**
+ * Wait until create was called {@code count} times.
+ *
+ * @param count The number of create calls to expect.
+ */
+ private void waitForActivityCreateCallbackCalled(int count) {
+ waitForCallbackCallCount(mCreateActivityCallCounter, count,
+ "Did not get expected call to create.");
+ }
+
+ /**
+ * Reset all counters.
+ */
+ public void resetCounters() {
+ mCancelOperationCounter.reset();
+ mLayoutCallCounter.reset();
+ mWriteCallCounter.reset();
+ mWriteCancelCallCounter.reset();
+ mStartCallCounter.reset();
+ mFinishCallCounter.reset();
+ mPrintJobQueuedCallCounter.reset();
+ mCreateSessionCallCounter.reset();
+ mDestroySessionCallCounter.reset();
+ mDestroyActivityCallCounter.reset();
+ mCreateActivityCallCounter.reset();
+ }
+
+ /**
+ * Wait until the message is shown that indicates that a printer is unavailable.
+ *
+ * @throws Exception If anything was unexpected.
+ */
+ protected void waitForPrinterUnavailable() throws Exception {
+ final String printerUnavailableMessage =
+ getPrintSpoolerString("print_error_printer_unavailable");
+
+ UiObject message = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/message"));
+ if (!message.getText().equals(printerUnavailableMessage)) {
+ throw new Exception("Wrong message: " + message.getText() + " instead of "
+ + printerUnavailableMessage);
+ }
+ }
+
+ protected void selectPrinter(String printerName) throws UiObjectNotFoundException, IOException {
+ try {
+ long delay = 1;
+ while (true) {
+ try {
+ UiDevice uiDevice = getUiDevice();
+ UiObject destinationSpinner = uiDevice.findObject(new UiSelector()
+ .resourceId("com.android.printspooler:id/destination_spinner"));
+
+ destinationSpinner.click();
+ getUiDevice().waitForIdle();
+
+ // Give spinner some time to expand
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+
+ // try to select printer
+ UiObject printerOption = uiDevice.findObject(
+ new UiSelector().text(printerName));
+ printerOption.click();
+ } catch (UiObjectNotFoundException e) {
+ Log.e(LOG_TAG, "Could not select printer " + printerName, e);
+ }
+
+ getUiDevice().waitForIdle();
+
+ if (!printerName.equals("All printers…")) {
+ // Make sure printer is selected
+ if (getUiDevice().hasObject(By.text(printerName))) {
+ break;
+ } else {
+ if (delay <= OPERATION_TIMEOUT_MILLIS) {
+ Log.w(LOG_TAG, "Cannot find printer " + printerName + ", retrying.");
+ delay *= 2;
+ } else {
+ throw new UiObjectNotFoundException(
+ "Could find printer " + printerName
+ + " even though we retried");
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void answerPrintServicesWarning(boolean confirm) throws UiObjectNotFoundException {
+ UiObject button;
+ if (confirm) {
+ button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button1"));
+ } else {
+ button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button2"));
+ }
+ button.click();
+ }
+
+ protected void changeOrientation(String orientation) throws UiObjectNotFoundException,
+ IOException {
+ try {
+ UiDevice uiDevice = getUiDevice();
+ UiObject orientationSpinner = uiDevice.findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/orientation_spinner"));
+ orientationSpinner.click();
+ UiObject orientationOption = uiDevice.findObject(new UiSelector().text(orientation));
+ orientationOption.click();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ public String getOrientation() throws UiObjectNotFoundException, IOException {
+ try {
+ UiObject orientationSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/orientation_spinner"));
+ return orientationSpinner.getText();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException, IOException {
+ try {
+ UiDevice uiDevice = getUiDevice();
+ UiObject mediaSizeSpinner = uiDevice.findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/paper_size_spinner"));
+ mediaSizeSpinner.click();
+ UiObject mediaSizeOption = uiDevice.findObject(new UiSelector().text(mediaSize));
+ mediaSizeOption.click();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void changeColor(String color) throws UiObjectNotFoundException, IOException {
+ try {
+ UiDevice uiDevice = getUiDevice();
+ UiObject colorSpinner = uiDevice.findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/color_spinner"));
+ colorSpinner.click();
+ UiObject colorOption = uiDevice.findObject(new UiSelector().text(color));
+ colorOption.click();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ public String getColor() throws UiObjectNotFoundException, IOException {
+ try {
+ UiObject colorSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/color_spinner"));
+ return colorSpinner.getText();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void changeDuplex(String duplex) throws UiObjectNotFoundException, IOException {
+ try {
+ UiDevice uiDevice = getUiDevice();
+ UiObject duplexSpinner = uiDevice.findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/duplex_spinner"));
+ duplexSpinner.click();
+ UiObject duplexOption = uiDevice.findObject(new UiSelector().text(duplex));
+ duplexOption.click();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void changeCopies(int newCopies) throws UiObjectNotFoundException, IOException {
+ try {
+ UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/copies_edittext"));
+ copies.setText(Integer.valueOf(newCopies).toString());
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected String getCopies() throws UiObjectNotFoundException, IOException {
+ try {
+ UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/copies_edittext"));
+ return copies.getText();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void assertNoPrintButton() throws UiObjectNotFoundException, IOException {
+ assertFalse(getUiDevice().hasObject(By.res("com.android.printspooler:id/print_button")));
+ }
+
+ public void clickPrintButton() throws UiObjectNotFoundException, IOException {
+ try {
+ UiObject printButton = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/print_button"));
+ printButton.click();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ protected void clickRetryButton() throws UiObjectNotFoundException, IOException {
+ try {
+ UiObject retryButton = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/action_button"));
+ retryButton.click();
+ } catch (UiObjectNotFoundException e) {
+ dumpWindowHierarchy();
+ throw e;
+ }
+ }
+
+ public void dumpWindowHierarchy() throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ getUiDevice().dumpWindowHierarchy(os);
+
+ Log.w(LOG_TAG, "Window hierarchy:");
+ for (String line : os.toString("UTF-8").split("\n")) {
+ Log.w(LOG_TAG, line);
+ }
+ }
+
+ protected PrintDocumentActivity getActivity() {
+ return mActivity;
+ }
+
+ protected void createActivity() throws IOException {
+ Log.d(LOG_TAG, "createActivity()");
+
+ int createBefore = getActivityCreateCallbackCallCount();
+
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.putExtra(TEST_ID, mTestId);
+
+ Instrumentation instrumentation = getInstrumentation();
+
+ // Unlock screen.
+ SystemUtil.runShellCommand(instrumentation, "input keyevent KEYCODE_WAKEUP");
+
+ intent.setClassName(instrumentation.getTargetContext().getPackageName(),
+ PrintDocumentActivity.class.getName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ instrumentation.startActivitySync(intent);
+
+ waitForActivityCreateCallbackCalled(createBefore + 1);
+ }
+
+ protected void openPrintOptions() throws UiObjectNotFoundException {
+ UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/expand_collapse_handle"));
+ expandHandle.click();
+ }
+
+ protected void openCustomPrintOptions() throws UiObjectNotFoundException {
+ UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/more_options_button"));
+ expandHandle.click();
+ }
+
+ protected static void clearPrintSpoolerData() throws Exception {
+ if (getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_PRINTING)) {
+ assertTrue("failed to clear print spooler data",
+ SystemUtil.runShellCommand(getInstrumentation(), String.format(
+ "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
+ .contains(PM_CLEAR_SUCCESS_OUTPUT));
+ }
+ }
+
+ protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
+ PrintAttributes oldAttributes, PrintAttributes newAttributes,
+ final boolean forPreview) {
+ inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
+ any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
+ new BaseMatcher<Bundle>() {
+ @Override
+ public boolean matches(Object item) {
+ Bundle bundle = (Bundle) item;
+ return forPreview == bundle.getBoolean(
+ PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ /* do nothing */
+ }
+ }));
+ }
+
+ protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
+ Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
+ // Create a mock print adapter.
+ PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
+ if (layoutAnswer != null) {
+ doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
+ any(PrintAttributes.class), any(CancellationSignal.class),
+ any(LayoutResultCallback.class), any(Bundle.class));
+ }
+ if (writeAnswer != null) {
+ doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
+ any(ParcelFileDescriptor.class), any(CancellationSignal.class),
+ any(WriteResultCallback.class));
+ }
+ if (finishAnswer != null) {
+ doAnswer(finishAnswer).when(adapter).onFinish();
+ }
+ return adapter;
+ }
+
+ /**
+ * Create a mock {@link PrintDocumentAdapter} that provides one empty page.
+ *
+ * @return The mock adapter
+ */
+ public @NonNull PrintDocumentAdapter createDefaultPrintDocumentAdapter(int numPages) {
+ final PrintAttributes[] printAttributes = new PrintAttributes[1];
+
+ return createMockPrintDocumentAdapter(
+ invocation -> {
+ PrintAttributes oldAttributes = (PrintAttributes) invocation.getArguments()[0];
+ printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
+ LayoutResultCallback callback =
+ (LayoutResultCallback) invocation
+ .getArguments()[3];
+
+ callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
+ .setPageCount(numPages).build(),
+ !oldAttributes.equals(printAttributes[0]));
+
+ oldAttributes = printAttributes[0];
+
+ onLayoutCalled();
+ return null;
+ }, invocation -> {
+ Object[] args = invocation.getArguments();
+ PageRange[] pages = (PageRange[]) args[0];
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
+ WriteResultCallback callback =
+ (WriteResultCallback) args[3];
+
+ writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
+ fd.close();
+ callback.onWriteFinished(pages);
+
+ onWriteCalled();
+ return null;
+ }, invocation -> {
+ onFinishCalled();
+ return null;
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
+ Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
+ Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
+ Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
+ Answer<Void> onDestroy) {
+ PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
+
+ doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
+ when(callbacks.getSession()).thenCallRealMethod();
+
+ if (onStartPrinterDiscovery != null) {
+ doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
+ any(List.class));
+ }
+ if (onStopPrinterDiscovery != null) {
+ doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
+ }
+ if (onValidatePrinters != null) {
+ doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
+ any(List.class));
+ }
+ if (onStartPrinterStateTracking != null) {
+ doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
+ any(PrinterId.class));
+ }
+ if (onRequestCustomPrinterIcon != null) {
+ doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
+ any(PrinterId.class), any(CancellationSignal.class),
+ any(CustomPrinterIconCallback.class));
+ }
+ if (onStopPrinterStateTracking != null) {
+ doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
+ any(PrinterId.class));
+ }
+ if (onDestroy != null) {
+ doAnswer(onDestroy).when(callbacks).onDestroy();
+ }
+
+ return callbacks;
+ }
+
+ protected PrintServiceCallbacks createMockPrintServiceCallbacks(
+ Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
+ Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
+ final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
+
+ doCallRealMethod().when(service).setService(any(StubbablePrintService.class));
+ when(service.getService()).thenCallRealMethod();
+
+ if (onCreatePrinterDiscoverySessionCallbacks != null) {
+ doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
+ .onCreatePrinterDiscoverySessionCallbacks();
+ }
+ if (onPrintJobQueued != null) {
+ doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
+ }
+ if (onRequestCancelPrintJob != null) {
+ doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
+ any(PrintJob.class));
+ }
+
+ return service;
+ }
+
+ protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
+ int fromIndex, int toIndex) throws IOException {
+ PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
+ final int pageCount = toIndex - fromIndex + 1;
+ for (int i = 0; i < pageCount; i++) {
+ PdfDocument.Page page = document.startPage(i);
+ document.finishPage(page);
+ }
+ FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
+ document.writeTo(fos);
+ fos.flush();
+ document.close();
+ }
+
+ protected void selectPages(String pages, int totalPages) throws Exception {
+ UiObject pagesSpinner = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/range_options_spinner"));
+ pagesSpinner.click();
+
+ UiObject rangeOption = getUiDevice().findObject(new UiSelector().textContains(
+ getPrintSpoolerStringOneParam("template_page_range", totalPages)));
+ rangeOption.click();
+
+ UiObject pagesEditText = getUiDevice().findObject(new UiSelector().resourceId(
+ "com.android.printspooler:id/page_range_edittext"));
+ pagesEditText.setText(pages);
+
+ // Hide the keyboard.
+ getUiDevice().pressBack();
+ }
+
+ private static final class CallCounter {
+ private final Object mLock = new Object();
+
+ private int mCallCount;
+
+ public void call() {
+ synchronized (mLock) {
+ mCallCount++;
+ mLock.notifyAll();
+ }
+ }
+
+ int getCallCount() {
+ synchronized (mLock) {
+ return mCallCount;
+ }
+ }
+
+ public void reset() {
+ synchronized (mLock) {
+ mCallCount = 0;
+ }
+ }
+
+ public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (mCallCount < count) {
+ try {
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException();
+ }
+ mLock.wait(timeoutMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Make {@code printerName} the default printer by adding it to the history of printers by
+ * printing once.
+ *
+ * @param adapter The {@link PrintDocumentAdapter} used
+ * @throws Exception If the printer could not be made default
+ */
+ public void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
+ throws Exception {
+ // Perform a full print operation on the printer
+ Log.d(LOG_TAG, "print");
+ print(adapter);
+ Log.d(LOG_TAG, "waitForWriteAdapterCallback");
+ waitForWriteAdapterCallback(1);
+ Log.d(LOG_TAG, "selectPrinter");
+ selectPrinter(printerName);
+ Log.d(LOG_TAG, "clickPrintButton");
+ clickPrintButton();
+ Log.d(LOG_TAG, "answerPrintServicesWarning");
+ answerPrintServicesWarning(true);
+ Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
+ waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+
+ // Switch to new activity, which should now use the default printer
+ Log.d(LOG_TAG, "getActivity().finish()");
+ getActivity().finish();
+
+ createActivity();
+ }
+
+ /**
+ * Get a string array from the print spooler's resources.
+ *
+ * @param resourceName The name of the array resource
+ * @return The localized string array
+ *
+ * @throws Exception If anything is unexpected
+ */
+ protected String[] getPrintSpoolerStringArray(String resourceName) throws Exception {
+ PackageManager pm = getActivity().getPackageManager();
+ Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+ int id = printSpoolerRes.getIdentifier(resourceName, "array", PRINTSPOOLER_PACKAGE);
+ return printSpoolerRes.getStringArray(id);
+ }
+
+ /**
+ * Get a string from the print spooler's resources.
+ *
+ * @param resourceName The name of the string resource
+ * @return The localized string
+ *
+ * @throws Exception If anything is unexpected
+ */
+ private String getPrintSpoolerString(String resourceName) throws Exception {
+ PackageManager pm = getActivity().getPackageManager();
+ Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+ int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
+ return printSpoolerRes.getString(id);
+ }
+
+ /**
+ * Get a string with one parameter from the print spooler's resources.
+ *
+ * @param resourceName The name of the string resource
+ * @return The localized string
+ *
+ * @throws Exception If anything is unexpected
+ */
+ protected String getPrintSpoolerStringOneParam(String resourceName, Object p)
+ throws Exception {
+ PackageManager pm = getActivity().getPackageManager();
+ Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+ int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
+ return printSpoolerRes.getString(id, p);
+ }
+
+ /**
+ * Get the default media size for the current locale.
+ *
+ * @return The default media size for the current locale
+ *
+ * @throws Exception If anything is unexpected
+ */
+ protected PrintAttributes.MediaSize getDefaultMediaSize() throws Exception {
+ PackageManager pm = getActivity().getPackageManager();
+ Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
+ int defaultMediaSizeResId = printSpoolerRes.getIdentifier("mediasize_default", "string",
+ PRINTSPOOLER_PACKAGE);
+ String defaultMediaSizeName = printSpoolerRes.getString(defaultMediaSizeResId);
+
+ switch (defaultMediaSizeName) {
+ case "NA_LETTER":
+ return PrintAttributes.MediaSize.NA_LETTER;
+ case "JIS_B5":
+ return PrintAttributes.MediaSize.JIS_B5;
+ case "ISO_A4":
+ return PrintAttributes.MediaSize.ISO_A4;
+ default:
+ throw new Exception("Unknown default media size " + defaultMediaSizeName);
+ }
+ }
+
+ /**
+ * Annotation used to signal that a test does not need an activity.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ protected @interface NoActivity { }
+
+ /**
+ * Rule that handles the {@link NoActivity} annotation.
+ */
+ private static class ShouldStartActivity implements TestRule {
+ boolean mNoActivity;
+
+ @Override
+ public Statement apply(Statement base, org.junit.runner.Description description) {
+ for (Annotation annotation : description.getAnnotations()) {
+ if (annotation instanceof NoActivity) {
+ mNoActivity = true;
+ break;
+ }
+ }
+
+ return base;
+ }
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/PrintDocumentActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/PrintDocumentActivity.java
new file mode 100644
index 0000000..cfb644f
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/PrintDocumentActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.print.test;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+public class PrintDocumentActivity extends Activity {
+ private static final String LOG_TAG = "PrintDocumentActivity";
+ int mTestId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mTestId = getIntent().getIntExtra(BasePrintTest.TEST_ID, -1);
+ Log.d(LOG_TAG, "onCreate() " + this + " for " + mTestId);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+ KeyguardManager km = getSystemService(KeyguardManager.class);
+ if (km != null) {
+ km.requestDismissKeyguard(this, null);
+ }
+
+ BasePrintTest.onActivityCreateCalled(mTestId, this);
+
+ if (savedInstanceState != null) {
+ Log.d(LOG_TAG,
+ "We cannot deal with lifecycle. Hence finishing " + this + " for " + mTestId);
+ finish();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(LOG_TAG, "onDestroy() " + this);
+ BasePrintTest.onActivityDestroyCalled(mTestId);
+ super.onDestroy();
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java b/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
new file mode 100644
index 0000000..dbc6644
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/Utils.java
@@ -0,0 +1,148 @@
+/*
+ * 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.print.test;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.print.PrintJob;
+import android.print.PrintManager;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+/**
+ * Utilities for print tests
+ */
+public class Utils {
+ private static final String LOG_TAG = "Utils";
+
+ /**
+ * A {@link Runnable} that can throw an {@link Throwable}.
+ */
+ public interface Invokable {
+ void run() throws Throwable;
+ }
+
+ /**
+ * Run a {@link Invokable} and expect and {@link Throwable} of a certain type.
+ *
+ * @param r The {@link Invokable} to run
+ * @param expectedClass The expected {@link Throwable} type
+ */
+ public static void assertException(@NonNull Invokable r,
+ @NonNull Class<? extends Throwable> expectedClass) throws Throwable {
+ try {
+ r.run();
+ } catch (Throwable e) {
+ if (e.getClass().isAssignableFrom(expectedClass)) {
+ return;
+ } else {
+ Log.e(LOG_TAG, "Expected: " + expectedClass.getName() + ", got: "
+ + e.getClass().getName());
+ throw e;
+ }
+ }
+
+ throw new AssertionError("No throwable thrown");
+ }
+
+ /**
+ * Run a {@link Invokable} on the main thread and forward the {@link Throwable} if one was
+ * thrown.
+ *
+ * @param r The {@link Invokable} to run
+ *
+ * @throws Throwable If the {@link Runnable} caused an issue
+ */
+ public static void runOnMainThread(@NonNull final Invokable r) throws Throwable {
+ final Object synchronizer = new Object();
+ final Throwable[] thrown = new Throwable[1];
+
+ synchronized (synchronizer) {
+ (new Handler(Looper.getMainLooper())).post(() -> {
+ synchronized (synchronizer) {
+ try {
+ r.run();
+ } catch (Throwable t) {
+ thrown[0] = t;
+ }
+
+ synchronizer.notify();
+ }
+ });
+
+ synchronizer.wait();
+ }
+
+ if (thrown[0] != null) {
+ throw thrown[0];
+ }
+ }
+
+ /**
+ * Make sure that a {@link Invokable} eventually finishes without throwing a {@link Throwable}.
+ *
+ * @param r The {@link Invokable} to run.
+ */
+ public static void eventually(@NonNull Invokable r) throws Throwable {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ r.run();
+ break;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < BasePrintTest.OPERATION_TIMEOUT_MILLIS) {
+ Log.e(LOG_TAG, "Ignoring exception", e);
+
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e1) {
+ Log.e(LOG_TAG, "Interrupted", e);
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param name Name of print job
+ *
+ * @return The print job for the name
+ *
+ * @throws Exception If print job could not be found
+ */
+ public static @NonNull PrintJob getPrintJob(@NonNull PrintManager pm, @NonNull String name)
+ throws Exception {
+ for (android.print.PrintJob job : pm.getPrintJobs()) {
+ if (job.getInfo().getLabel().equals(name)) {
+ return job;
+ }
+ }
+
+ throw new Exception("Print job " + name + " not found in " + pm.getPrintJobs());
+ }
+
+ /**
+ * @return The print manager
+ */
+ public static @NonNull PrintManager getPrintManager(@NonNull Context context) {
+ return (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/AddPrintersActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/AddPrintersActivity.java
new file mode 100644
index 0000000..5e01d7c
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/AddPrintersActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.print.test.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+public class AddPrintersActivity extends Activity {
+ private static final ArrayList<Runnable> sObservers = new ArrayList<>();
+
+ public static void addObserver(@NonNull Runnable observer) {
+ synchronized (sObservers) {
+ sObservers.add(observer);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ synchronized (sObservers) {
+ for (Runnable sObserver : sObservers) {
+ sObserver.run();
+ }
+ }
+
+ finish();
+ }
+
+ public static void clearObservers() {
+ synchronized (sObservers) {
+ sObservers.clear();
+ }
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/CustomPrintOptionsActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/CustomPrintOptionsActivity.java
new file mode 100644
index 0000000..94993c7
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/CustomPrintOptionsActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.print.test.services;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.print.PrintJobInfo;
+import android.print.PrinterInfo;
+import android.printservice.PrintService;
+
+/**
+ * Custom print options activity for both print services
+ */
+public class CustomPrintOptionsActivity extends Activity {
+ /** Lock for {@link #sCallback} */
+ private static final Object sLock = new Object();
+
+ /** Currently registered callback for _both_ first and second print service. */
+ private static CustomPrintOptionsCallback sCallback = null;
+
+ /**
+ * Set a new callback called when the custom options activity is launched.
+ *
+ * @param callback The new callback or null, if the callback should be unregistered.
+ */
+ public static void setCallBack(CustomPrintOptionsCallback callback) {
+ synchronized (sLock) {
+ sCallback = callback;
+ }
+ }
+
+ /**
+ * Callback executed for this activity. Set via {@link #setCallBack}.
+ */
+ public interface CustomPrintOptionsCallback {
+ PrintJobInfo executeCustomPrintOptionsActivity(PrintJobInfo printJob,
+ PrinterInfo printer);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent result = new Intent();
+
+ synchronized (sLock) {
+ if (sCallback != null) {
+ PrintJobInfo printJobInfo = getIntent().getParcelableExtra(
+ PrintService.EXTRA_PRINT_JOB_INFO);
+ PrinterInfo printerInfo = getIntent().getParcelableExtra(
+ "android.intent.extra.print.EXTRA_PRINTER_INFO");
+
+ result.putExtra(PrintService.EXTRA_PRINT_JOB_INFO,
+ sCallback.executeCustomPrintOptionsActivity(printJobInfo, printerInfo));
+ }
+ }
+
+ setResult(Activity.RESULT_OK, result);
+ finish();
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/FirstPrintService.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/FirstPrintService.java
new file mode 100644
index 0000000..2c3a4ee
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/FirstPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.print.test.services;
+
+public class FirstPrintService extends StubbablePrintService {
+
+ private static final Object sLock = new Object();
+
+ private static PrintServiceCallbacks sCallbacks;
+
+ public static void setCallbacks(PrintServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ @Override
+ protected PrintServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/InfoActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/InfoActivity.java
new file mode 100644
index 0000000..627fec9
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/InfoActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.print.test.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+public class InfoActivity extends Activity {
+ public interface Observer {
+ void onCreate(Activity createdActivity);
+ }
+
+ private static final ArrayList<Observer> sObservers = new ArrayList<>();
+
+ public static void addObserver(Observer observer) {
+ synchronized (sObservers) {
+ sObservers.add(observer);
+ }
+ }
+
+ public static void clearObservers() {
+ synchronized (sObservers) {
+ sObservers.clear();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ synchronized (sObservers) {
+ ArrayList<Observer> observers = (ArrayList<Observer>) sObservers.clone();
+
+ for (Observer observer : observers) {
+ observer.onCreate(this);
+ }
+ }
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrintServiceCallbacks.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrintServiceCallbacks.java
new file mode 100644
index 0000000..d2d42f2
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrintServiceCallbacks.java
@@ -0,0 +1,38 @@
+/*
+ * 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.print.test.services;
+
+import android.printservice.PrintJob;
+
+public abstract class PrintServiceCallbacks {
+
+ private StubbablePrintService mService;
+
+ public StubbablePrintService getService() {
+ return mService;
+ }
+
+ public void setService(StubbablePrintService service) {
+ mService = service;
+ }
+
+ public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
+
+ public abstract void onRequestCancelPrintJob(PrintJob printJob);
+
+ public abstract void onPrintJobQueued(PrintJob printJob);
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrinterDiscoverySessionCallbacks.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrinterDiscoverySessionCallbacks.java
new file mode 100644
index 0000000..7b7a1b9
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/PrinterDiscoverySessionCallbacks.java
@@ -0,0 +1,51 @@
+/*
+ * 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.print.test.services;
+
+import android.os.CancellationSignal;
+import android.print.PrinterId;
+import android.printservice.CustomPrinterIconCallback;
+
+import java.util.List;
+
+public abstract class PrinterDiscoverySessionCallbacks {
+
+ private StubbablePrinterDiscoverySession mSession;
+
+ public void setSession(StubbablePrinterDiscoverySession session) {
+ mSession = session;
+ }
+
+ public StubbablePrinterDiscoverySession getSession() {
+ return mSession;
+ }
+
+ public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
+
+ public abstract void onStopPrinterDiscovery();
+
+ public abstract void onValidatePrinters(List<PrinterId> printerIds);
+
+ public abstract void onStartPrinterStateTracking(PrinterId printerId);
+
+ public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
+ CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
+
+ public abstract void onStopPrinterStateTracking(PrinterId printerId);
+
+ public abstract void onDestroy();
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/SecondPrintService.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SecondPrintService.java
new file mode 100644
index 0000000..3e8a6a7
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SecondPrintService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.print.test.services;
+
+public class SecondPrintService extends StubbablePrintService {
+
+ private static final Object sLock = new Object();
+
+ private static PrintServiceCallbacks sCallbacks;
+
+ public static void setCallbacks(PrintServiceCallbacks callbacks) {
+ synchronized (sLock) {
+ sCallbacks = callbacks;
+ }
+ }
+
+ @Override
+ protected PrintServiceCallbacks getCallbacks() {
+ synchronized (sLock) {
+ if (sCallbacks != null) {
+ sCallbacks.setService(this);
+ }
+ return sCallbacks;
+ }
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/SettingsActivity.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SettingsActivity.java
new file mode 100644
index 0000000..56c63fa
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/SettingsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.print.test.services;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SettingsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java
new file mode 100644
index 0000000..09d1f78
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrintService.java
@@ -0,0 +1,64 @@
+/*
+ * 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.print.test.services;
+
+import android.content.Context;
+import android.printservice.PrintJob;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+
+import java.util.List;
+
+public abstract class StubbablePrintService extends PrintService {
+
+ @Override
+ public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ return new StubbablePrinterDiscoverySession(this,
+ getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
+ }
+ return null;
+ }
+
+ @Override
+ public void onRequestCancelPrintJob(PrintJob printJob) {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.onRequestCancelPrintJob(printJob);
+ }
+ }
+
+ @Override
+ public void onPrintJobQueued(PrintJob printJob) {
+ PrintServiceCallbacks callbacks = getCallbacks();
+ if (callbacks != null) {
+ callbacks.onPrintJobQueued(printJob);
+ }
+ }
+
+ protected abstract PrintServiceCallbacks getCallbacks();
+
+ public void callAttachBaseContext(Context base) {
+ attachBaseContext(base);
+ }
+
+ public List<PrintJob> callGetActivePrintJobs() {
+ return getActivePrintJobs();
+ }
+
+}
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrinterDiscoverySession.java b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrinterDiscoverySession.java
new file mode 100644
index 0000000..c9fb015
--- /dev/null
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/services/StubbablePrinterDiscoverySession.java
@@ -0,0 +1,95 @@
+/*
+ * 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.print.test.services;
+
+import android.os.CancellationSignal;
+import android.print.PrinterId;
+import android.printservice.CustomPrinterIconCallback;
+import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
+ private final PrintService mService;
+ private final PrinterDiscoverySessionCallbacks mCallbacks;
+
+ public StubbablePrinterDiscoverySession(PrintService service,
+ PrinterDiscoverySessionCallbacks callbacks) {
+ mService = service;
+ mCallbacks = callbacks;
+ if (mCallbacks != null) {
+ mCallbacks.setSession(this);
+ }
+ }
+
+ public PrintService getService() {
+ return mService;
+ }
+
+ @Override
+ public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
+ if (mCallbacks != null) {
+ mCallbacks.onStartPrinterDiscovery(priorityList);
+ }
+ }
+
+ @Override
+ public void onStopPrinterDiscovery() {
+ if (mCallbacks != null) {
+ mCallbacks.onStopPrinterDiscovery();
+ }
+ }
+
+ @Override
+ public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
+ if (mCallbacks != null) {
+ mCallbacks.onValidatePrinters(printerIds);
+ }
+ }
+
+ @Override
+ public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
+ if (mCallbacks != null) {
+ mCallbacks.onStartPrinterStateTracking(printerId);
+ }
+ }
+
+ @Override
+ public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull CustomPrinterIconCallback callback) {
+ if (mCallbacks != null) {
+ mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
+ }
+ }
+
+ @Override
+ public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
+ if (mCallbacks != null) {
+ mCallbacks.onStopPrinterStateTracking(printerId);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mCallbacks != null) {
+ mCallbacks.onDestroy();
+ }
+ }
+}
diff --git a/tests/tests/print/res/values/themes.xml b/tests/tests/print/res/values/themes.xml
index f5c9b977..07e228c 100644
--- a/tests/tests/print/res/values/themes.xml
+++ b/tests/tests/print/res/values/themes.xml
@@ -17,7 +17,12 @@
-->
<resources>
- <style name="Theme.Translucent" parent="@android:style/Theme.DeviceDefault">
+ <style name="NoAnimationTranslucent" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+
+ <style name="NoAnimation" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowAnimationStyle">@null</item>
</style>
</resources>
diff --git a/tests/tests/print/res/xml/printservice.xml b/tests/tests/print/res/xml/printservice.xml
index 78c7504..07f7d6c 100644
--- a/tests/tests/print/res/xml/printservice.xml
+++ b/tests/tests/print/res/xml/printservice.xml
@@ -17,6 +17,6 @@
-->
<print-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:settingsActivity="android.print.cts.services.SettingsActivity"
- android:addPrintersActivity="android.print.cts.services.AddPrintersActivity"
- android:advancedPrintOptionsActivity="android.print.cts.services.CustomPrintOptionsActivity"/>
+ android:settingsActivity="android.print.test.services.SettingsActivity"
+ android:addPrintersActivity="android.print.test.services.AddPrintersActivity"
+ android:advancedPrintOptionsActivity="android.print.test.services.CustomPrintOptionsActivity"/>
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
deleted file mode 100644
index db1efd7..0000000
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ /dev/null
@@ -1,1105 +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 android.print.cts;
-
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.GET_SERVICES;
-import static android.print.cts.Utils.getPrintManager;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.pdf.PdfDocument;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
-import android.print.PrintDocumentInfo;
-import android.print.PrinterId;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.StubbablePrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
-import android.print.pdf.PrintedPdfDocument;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintJob;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.Statement;
-import org.mockito.InOrder;
-import org.mockito.stubbing.Answer;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This is the base class for print tests.
- */
-public abstract class BasePrintTest {
- private final static String LOG_TAG = "BasePrintTest";
-
- static final long OPERATION_TIMEOUT_MILLIS = 60000;
- static final String PRINT_JOB_NAME = "Test";
- static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID";
-
- private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
- private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
- private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
- private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
- private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
- private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
- private static final String PRINTSPOOLER_PACKAGE = "com.android.printspooler";
-
- private static final AtomicInteger sLastTestID = new AtomicInteger();
- private int mTestId;
- private PrintDocumentActivity mActivity;
-
- private static String sDisabledPrintServicesBefore;
-
- private static final SparseArray<BasePrintTest> sIdToTest = new SparseArray<>();
-
- public final @Rule ShouldStartActivity mShouldStartActivityRule = new ShouldStartActivity();
-
- /**
- * Return the UI device
- *
- * @return the UI device
- */
- public UiDevice getUiDevice() {
- return UiDevice.getInstance(getInstrumentation());
- }
-
- private CallCounter mCancelOperationCounter;
- private CallCounter mLayoutCallCounter;
- private CallCounter mWriteCallCounter;
- private CallCounter mWriteCancelCallCounter;
- private CallCounter mFinishCallCounter;
- private CallCounter mPrintJobQueuedCallCounter;
- private CallCounter mCreateSessionCallCounter;
- private CallCounter mDestroySessionCallCounter;
- private CallCounter mDestroyActivityCallCounter = new CallCounter();
- private CallCounter mCreateActivityCallCounter = new CallCounter();
-
- private static String[] sEnabledImes;
-
- private static String[] getEnabledImes() throws IOException {
- List<String> imeList = new ArrayList<>();
-
- ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
- .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
-
- String line;
- while ((line = reader.readLine()) != null) {
- imeList.add(line);
- }
- }
-
- String[] imeArray = new String[imeList.size()];
- imeList.toArray(imeArray);
-
- return imeArray;
- }
-
- private static void disableImes() throws Exception {
- sEnabledImes = getEnabledImes();
- for (String ime : sEnabledImes) {
- String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
- SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
- }
- }
-
- private static void enableImes() throws Exception {
- for (String ime : sEnabledImes) {
- String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
- SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
- }
- sEnabledImes = null;
- }
-
- protected static Instrumentation getInstrumentation() {
- return InstrumentationRegistry.getInstrumentation();
- }
-
- @BeforeClass
- public static void setUpClass() throws Exception {
- Log.d(LOG_TAG, "setUpClass()");
-
- Instrumentation instrumentation = getInstrumentation();
-
- // Make sure we start with a clean slate.
- Log.d(LOG_TAG, "clearPrintSpoolerData()");
- clearPrintSpoolerData();
- Log.d(LOG_TAG, "disableImes()");
- disableImes();
- Log.d(LOG_TAG, "disablePrintServices()");
- disablePrintServices(instrumentation.getTargetContext().getPackageName());
-
- // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
- // Dexmaker is used by mockito.
- System.setProperty("dexmaker.dexcache", instrumentation
- .getTargetContext().getCacheDir().getPath());
-
- Log.d(LOG_TAG, "setUpClass() done");
- }
-
- /**
- * Disable all print services beside the ones we want to leave enabled.
- *
- * @param packageToLeaveEnabled The package of the services to leave enabled.
- */
- private static void disablePrintServices(@NonNull String packageToLeaveEnabled)
- throws IOException {
- Instrumentation instrumentation = getInstrumentation();
-
- sDisabledPrintServicesBefore = SystemUtil.runShellCommand(instrumentation,
- "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
-
- Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
- List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager()
- .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA);
-
- StringBuilder builder = new StringBuilder();
- for (ResolveInfo service : installedServices) {
- if (packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
- continue;
- }
- if (builder.length() > 0) {
- builder.append(":");
- }
- builder.append(new ComponentName(service.serviceInfo.packageName,
- service.serviceInfo.name).flattenToString());
- }
-
- SystemUtil.runShellCommand(instrumentation, "settings put secure "
- + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
- }
-
- /**
- * Revert {@link #disablePrintServices(String)}
- */
- private static void enablePrintServices() throws IOException {
- SystemUtil.runShellCommand(getInstrumentation(),
- "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
- + sDisabledPrintServicesBefore);
- }
-
- @Before
- public void setUp() throws Exception {
- Log.d(LOG_TAG, "setUp()");
-
- assumeTrue(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_PRINTING));
-
- // Initialize the latches.
- Log.d(LOG_TAG, "init counters");
- mCancelOperationCounter = new CallCounter();
- mLayoutCallCounter = new CallCounter();
- mFinishCallCounter = new CallCounter();
- mWriteCallCounter = new CallCounter();
- mWriteCancelCallCounter = new CallCounter();
- mFinishCallCounter = new CallCounter();
- mPrintJobQueuedCallCounter = new CallCounter();
- mCreateSessionCallCounter = new CallCounter();
- mDestroySessionCallCounter = new CallCounter();
-
- mTestId = sLastTestID.incrementAndGet();
- sIdToTest.put(mTestId, this);
-
- // Create the activity if needed
- if (!mShouldStartActivityRule.noActivity) {
- createActivity();
- }
-
- Log.d(LOG_TAG, "setUp() done");
- }
-
- @After
- public void tearDown() throws Exception {
- Log.d(LOG_TAG, "tearDown()");
-
- finishActivity();
-
- sIdToTest.remove(mTestId);
-
- Log.d(LOG_TAG, "tearDown() done");
- }
-
- @AfterClass
- public static void tearDownClass() throws Exception {
- Log.d(LOG_TAG, "tearDownClass()");
-
- Instrumentation instrumentation = getInstrumentation();
-
- Log.d(LOG_TAG, "enablePrintServices()");
- enablePrintServices();
-
- Log.d(LOG_TAG, "enableImes()");
- enableImes();
-
- // Make sure the spooler is cleaned, this also un-approves all services
- Log.d(LOG_TAG, "clearPrintSpoolerData()");
- clearPrintSpoolerData();
-
- SystemUtil.runShellCommand(instrumentation, "settings put secure "
- + Settings.Secure.DISABLED_PRINT_SERVICES + " null");
-
- Log.d(LOG_TAG, "tearDownClass() done");
- }
-
- protected void print(final PrintDocumentAdapter adapter, final PrintAttributes attributes) {
- print(adapter, "Print job", attributes);
- }
-
- protected void print(PrintDocumentAdapter adapter) {
- print(adapter, (PrintAttributes) null);
- }
-
- protected void print(PrintDocumentAdapter adapter, String printJobName) {
- print(adapter, printJobName, null);
- }
-
- /**
- * Start printing
- *
- * @param adapter Adapter supplying data to print
- * @param printJobName The name of the print job
- */
- protected void print(@NonNull PrintDocumentAdapter adapter, @NonNull String printJobName,
- @Nullable PrintAttributes attributes) {
- // Initiate printing as if coming from the app.
- getInstrumentation()
- .runOnMainSync(() -> getPrintManager(getActivity()).print(printJobName, adapter,
- attributes));
- }
-
- void onCancelOperationCalled() {
- mCancelOperationCounter.call();
- }
-
- void onLayoutCalled() {
- mLayoutCallCounter.call();
- }
-
- int getWriteCallCount() {
- return mWriteCallCounter.getCallCount();
- }
-
- protected void onWriteCalled() {
- mWriteCallCounter.call();
- }
-
- protected void onWriteCancelCalled() {
- mWriteCancelCallCounter.call();
- }
-
- void onFinishCalled() {
- mFinishCallCounter.call();
- }
-
- void onPrintJobQueuedCalled() {
- mPrintJobQueuedCallCounter.call();
- }
-
- void onPrinterDiscoverySessionCreateCalled() {
- mCreateSessionCallCounter.call();
- }
-
- protected void onPrinterDiscoverySessionDestroyCalled() {
- mDestroySessionCallCounter.call();
- }
-
- void waitForCancelOperationCallbackCalled() {
- waitForCallbackCallCount(mCancelOperationCounter, 1,
- "Did not get expected call to onCancel for the current operation.");
- }
-
- void waitForPrinterDiscoverySessionCreateCallbackCalled() {
- waitForCallbackCallCount(mCreateSessionCallCounter, 1,
- "Did not get expected call to onCreatePrinterDiscoverySession.");
- }
-
- void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
- waitForCallbackCallCount(mDestroySessionCallCounter, count,
- "Did not get expected call to onDestroyPrinterDiscoverySession.");
- }
-
- void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
- waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
- "Did not get expected call to onPrintJobQueued.");
- }
-
- void waitForAdapterFinishCallbackCalled() {
- waitForCallbackCallCount(mFinishCallCounter, 1,
- "Did not get expected call to finish.");
- }
-
- void waitForLayoutAdapterCallbackCount(int count) {
- waitForCallbackCallCount(mLayoutCallCounter, count,
- "Did not get expected call to layout.");
- }
-
- void waitForWriteAdapterCallback(int count) {
- waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
- }
-
- void waitForWriteCancelCallback(int count) {
- waitForCallbackCallCount(mWriteCancelCallCounter, count,
- "Did not get expected cancel of write.");
- }
-
- private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
- try {
- counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS);
- } catch (TimeoutException te) {
- fail(message);
- }
- }
-
- /**
- * Indicate the print activity was created.
- */
- static void onActivityCreateCalled(int testId, PrintDocumentActivity activity) {
- synchronized (sIdToTest) {
- BasePrintTest test = sIdToTest.get(testId);
- if (test != null) {
- test.mActivity = activity;
- test.mCreateActivityCallCounter.call();
- }
- }
- }
-
- /**
- * Indicate the print activity was destroyed.
- */
- static void onActivityDestroyCalled(int testId) {
- synchronized (sIdToTest) {
- BasePrintTest test = sIdToTest.get(testId);
- if (test != null) {
- test.mDestroyActivityCallCounter.call();
- }
- }
- }
-
- private void finishActivity() {
- Activity activity = mActivity;
-
- if (activity != null) {
- if (!activity.isFinishing()) {
- activity.finish();
- }
-
- while (!activity.isDestroyed()) {
- int creates = mCreateActivityCallCounter.getCallCount();
- waitForCallbackCallCount(mDestroyActivityCallCounter, creates,
- "Activity was not destroyed");
- }
- }
- }
-
- /**
- * Get the number of ties the print activity was destroyed.
- *
- * @return The number of onDestroy calls on the print activity.
- */
- int getActivityDestroyCallbackCallCount() {
- return mDestroyActivityCallCounter.getCallCount();
- }
-
- /**
- * Get the number of ties the print activity was created.
- *
- * @return The number of onCreate calls on the print activity.
- */
- private int getActivityCreateCallbackCallCount() {
- return mCreateActivityCallCounter.getCallCount();
- }
-
- /**
- * Wait until create was called {@code count} times.
- *
- * @param count The number of create calls to expect.
- */
- private void waitForActivityCreateCallbackCalled(int count) {
- waitForCallbackCallCount(mCreateActivityCallCounter, count,
- "Did not get expected call to create.");
- }
-
- /**
- * Reset all counters.
- */
- void resetCounters() {
- mCancelOperationCounter.reset();
- mLayoutCallCounter.reset();
- mWriteCallCounter.reset();
- mWriteCancelCallCounter.reset();
- mFinishCallCounter.reset();
- mPrintJobQueuedCallCounter.reset();
- mCreateSessionCallCounter.reset();
- mDestroySessionCallCounter.reset();
- mDestroyActivityCallCounter.reset();
- mCreateActivityCallCounter.reset();
- }
-
- void selectPrinter(String printerName) throws UiObjectNotFoundException, IOException {
- try {
- long delay = 1;
- while (true) {
- try {
- UiDevice uiDevice = getUiDevice();
- UiObject destinationSpinner = uiDevice.findObject(new UiSelector()
- .resourceId("com.android.printspooler:id/destination_spinner"));
-
- destinationSpinner.click();
- getUiDevice().waitForIdle();
-
- // Give spinner some time to expand
- try {
- Thread.sleep(delay);
- } catch (InterruptedException e) {
- // ignore
- }
-
- // try to select printer
- UiObject printerOption = uiDevice.findObject(
- new UiSelector().text(printerName));
- printerOption.click();
- } catch (UiObjectNotFoundException e) {
- Log.e(LOG_TAG, "Could not select printer " + printerName, e);
- }
-
- getUiDevice().waitForIdle();
-
- if (!printerName.equals("All printers…")) {
- // Make sure printer is selected
- if (getUiDevice().hasObject(By.text(printerName))) {
- break;
- } else {
- if (delay <= OPERATION_TIMEOUT_MILLIS) {
- Log.w(LOG_TAG, "Cannot find printer " + printerName + ", retrying.");
- delay *= 2;
- } else {
- throw new UiObjectNotFoundException(
- "Could find printer " + printerName +
- " even though we retried");
- }
- }
- } else {
- break;
- }
- }
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void answerPrintServicesWarning(boolean confirm) throws UiObjectNotFoundException {
- UiObject button;
- if (confirm) {
- button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button1"));
- } else {
- button = getUiDevice().findObject(new UiSelector().resourceId("android:id/button2"));
- }
- button.click();
- }
-
- void changeOrientation(String orientation) throws UiObjectNotFoundException, IOException {
- try {
- UiDevice uiDevice = getUiDevice();
- UiObject orientationSpinner = uiDevice.findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/orientation_spinner"));
- orientationSpinner.click();
- UiObject orientationOption = uiDevice.findObject(new UiSelector().text(orientation));
- orientationOption.click();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- protected String getOrientation() throws UiObjectNotFoundException, IOException {
- try {
- UiObject orientationSpinner = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/orientation_spinner"));
- return orientationSpinner.getText();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void changeMediaSize(String mediaSize) throws UiObjectNotFoundException, IOException {
- try {
- UiDevice uiDevice = getUiDevice();
- UiObject mediaSizeSpinner = uiDevice.findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/paper_size_spinner"));
- mediaSizeSpinner.click();
- UiObject mediaSizeOption = uiDevice.findObject(new UiSelector().text(mediaSize));
- mediaSizeOption.click();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void changeColor(String color) throws UiObjectNotFoundException, IOException {
- try {
- UiDevice uiDevice = getUiDevice();
- UiObject colorSpinner = uiDevice.findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/color_spinner"));
- colorSpinner.click();
- UiObject colorOption = uiDevice.findObject(new UiSelector().text(color));
- colorOption.click();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- protected String getColor() throws UiObjectNotFoundException, IOException {
- try {
- UiObject colorSpinner = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/color_spinner"));
- return colorSpinner.getText();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void changeDuplex(String duplex) throws UiObjectNotFoundException, IOException {
- try {
- UiDevice uiDevice = getUiDevice();
- UiObject duplexSpinner = uiDevice.findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/duplex_spinner"));
- duplexSpinner.click();
- UiObject duplexOption = uiDevice.findObject(new UiSelector().text(duplex));
- duplexOption.click();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void changeCopies(int newCopies) throws UiObjectNotFoundException, IOException {
- try {
- UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/copies_edittext"));
- copies.setText(Integer.valueOf(newCopies).toString());
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- protected String getCopies() throws UiObjectNotFoundException, IOException {
- try {
- UiObject copies = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/copies_edittext"));
- return copies.getText();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void assertNoPrintButton() throws UiObjectNotFoundException, IOException {
- assertFalse(getUiDevice().hasObject(By.res("com.android.printspooler:id/print_button")));
- }
-
- void clickPrintButton() throws UiObjectNotFoundException, IOException {
- try {
- UiObject printButton = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/print_button"));
- printButton.click();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void clickRetryButton() throws UiObjectNotFoundException, IOException {
- try {
- UiObject retryButton = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/action_button"));
- retryButton.click();
- } catch (UiObjectNotFoundException e) {
- dumpWindowHierarchy();
- throw e;
- }
- }
-
- void dumpWindowHierarchy() throws IOException {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- getUiDevice().dumpWindowHierarchy(os);
-
- Log.w(LOG_TAG, "Window hierarchy:");
- for (String line : os.toString("UTF-8").split("\n")) {
- Log.w(LOG_TAG, line);
- }
- }
-
- protected PrintDocumentActivity getActivity() {
- return mActivity;
- }
-
- protected void createActivity() {
- Log.d(LOG_TAG, "createActivity()");
-
- int createBefore = getActivityCreateCallbackCallCount();
-
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.putExtra(TEST_ID, mTestId);
-
- Instrumentation instrumentation = getInstrumentation();
- intent.setClassName(instrumentation.getTargetContext().getPackageName(),
- PrintDocumentActivity.class.getName());
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- instrumentation.startActivitySync(intent);
-
- waitForActivityCreateCallbackCalled(createBefore + 1);
- }
-
- void openPrintOptions() throws UiObjectNotFoundException {
- UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/expand_collapse_handle"));
- expandHandle.click();
- }
-
- void openCustomPrintOptions() throws UiObjectNotFoundException {
- UiObject expandHandle = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/more_options_button"));
- expandHandle.click();
- }
-
- static void clearPrintSpoolerData() throws Exception {
- if (getInstrumentation().getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_PRINTING)) {
- assertTrue("failed to clear print spooler data",
- SystemUtil.runShellCommand(getInstrumentation(), String.format(
- "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
- .contains(PM_CLEAR_SUCCESS_OUTPUT));
- }
- }
-
- void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
- PrintAttributes oldAttributes, PrintAttributes newAttributes,
- final boolean forPreview) {
- inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
- any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
- new BaseMatcher<Bundle>() {
- @Override
- public boolean matches(Object item) {
- Bundle bundle = (Bundle) item;
- return forPreview == bundle.getBoolean(
- PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
- }
-
- @Override
- public void describeTo(Description description) {
- /* do nothing */
- }
- }));
- }
-
- PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
- Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
- // Create a mock print adapter.
- PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
- if (layoutAnswer != null) {
- doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
- any(PrintAttributes.class), any(CancellationSignal.class),
- any(LayoutResultCallback.class), any(Bundle.class));
- }
- if (writeAnswer != null) {
- doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
- any(ParcelFileDescriptor.class), any(CancellationSignal.class),
- any(WriteResultCallback.class));
- }
- if (finishAnswer != null) {
- doAnswer(finishAnswer).when(adapter).onFinish();
- }
- return adapter;
- }
-
- /**
- * Create a mock {@link PrintDocumentAdapter} that provides one empty page.
- *
- * @return The mock adapter
- */
- @NonNull PrintDocumentAdapter createDefaultPrintDocumentAdapter(int numPages) {
- final PrintAttributes[] printAttributes = new PrintAttributes[1];
-
- return createMockPrintDocumentAdapter(
- invocation -> {
- PrintAttributes oldAttributes = (PrintAttributes) invocation.getArguments()[0];
- printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
- PrintDocumentAdapter.LayoutResultCallback callback =
- (PrintDocumentAdapter.LayoutResultCallback) invocation
- .getArguments()[3];
-
- callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
- .setPageCount(numPages).build(),
- !oldAttributes.equals(printAttributes[0]));
-
- oldAttributes = printAttributes[0];
-
- onLayoutCalled();
- return null;
- }, invocation -> {
- Object[] args = invocation.getArguments();
- PageRange[] pages = (PageRange[]) args[0];
- ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
- PrintDocumentAdapter.WriteResultCallback callback =
- (PrintDocumentAdapter.WriteResultCallback) args[3];
-
- writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
- fd.close();
- callback.onWriteFinished(pages);
-
- onWriteCalled();
- return null;
- }, invocation -> {
- onFinishCalled();
- return null;
- });
- }
-
- @SuppressWarnings("unchecked")
- protected static PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
- Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
- Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
- Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
- Answer<Void> onDestroy) {
- PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
-
- doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
- when(callbacks.getSession()).thenCallRealMethod();
-
- if (onStartPrinterDiscovery != null) {
- doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
- any(List.class));
- }
- if (onStopPrinterDiscovery != null) {
- doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
- }
- if (onValidatePrinters != null) {
- doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
- any(List.class));
- }
- if (onStartPrinterStateTracking != null) {
- doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
- any(PrinterId.class));
- }
- if (onRequestCustomPrinterIcon != null) {
- doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
- any(PrinterId.class), any(CancellationSignal.class),
- any(CustomPrinterIconCallback.class));
- }
- if (onStopPrinterStateTracking != null) {
- doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
- any(PrinterId.class));
- }
- if (onDestroy != null) {
- doAnswer(onDestroy).when(callbacks).onDestroy();
- }
-
- return callbacks;
- }
-
- protected PrintServiceCallbacks createMockPrintServiceCallbacks(
- Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
- Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
- final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
-
- doCallRealMethod().when(service).setService(any(StubbablePrintService.class));
- when(service.getService()).thenCallRealMethod();
-
- if (onCreatePrinterDiscoverySessionCallbacks != null) {
- doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
- .onCreatePrinterDiscoverySessionCallbacks();
- }
- if (onPrintJobQueued != null) {
- doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
- }
- if (onRequestCancelPrintJob != null) {
- doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
- any(PrintJob.class));
- }
-
- return service;
- }
-
- protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
- int fromIndex, int toIndex) throws IOException {
- PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
- final int pageCount = toIndex - fromIndex + 1;
- for (int i = 0; i < pageCount; i++) {
- PdfDocument.Page page = document.startPage(i);
- document.finishPage(page);
- }
- FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
- document.writeTo(fos);
- fos.flush();
- document.close();
- }
-
- protected void selectPages(String pages, int totalPages) throws Exception {
- UiObject pagesSpinner = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/range_options_spinner"));
- pagesSpinner.click();
-
- UiObject rangeOption = getUiDevice().findObject(new UiSelector().textContains(
- getPrintSpoolerStringOneParam("template_page_range", totalPages)));
- rangeOption.click();
-
- UiObject pagesEditText = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/page_range_edittext"));
- pagesEditText.setText(pages);
-
- // Hide the keyboard.
- getUiDevice().pressBack();
- }
-
- private static final class CallCounter {
- private final Object mLock = new Object();
-
- private int mCallCount;
-
- public void call() {
- synchronized (mLock) {
- mCallCount++;
- mLock.notifyAll();
- }
- }
-
- int getCallCount() {
- synchronized (mLock) {
- return mCallCount;
- }
- }
-
- public void reset() {
- synchronized (mLock) {
- mCallCount = 0;
- }
- }
-
- public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
- synchronized (mLock) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (mCallCount < count) {
- try {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new TimeoutException();
- }
- mLock.wait(timeoutMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
- }
-
-
- /**
- * Make {@code printerName} the default printer by adding it to the history of printers by
- * printing once.
- *
- * @param adapter The {@link PrintDocumentAdapter} used
- * @throws Exception If the printer could not be made default
- */
- void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
- throws Exception {
- // Perform a full print operation on the printer
- Log.d(LOG_TAG, "print");
- print(adapter);
- Log.d(LOG_TAG, "waitForWriteAdapterCallback");
- waitForWriteAdapterCallback(1);
- Log.d(LOG_TAG, "selectPrinter");
- selectPrinter(printerName);
- Log.d(LOG_TAG, "clickPrintButton");
- clickPrintButton();
- Log.d(LOG_TAG, "answerPrintServicesWarning");
- answerPrintServicesWarning(true);
- Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
- waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
-
- // Switch to new activity, which should now use the default printer
- Log.d(LOG_TAG, "getActivity().finish()");
- getActivity().finish();
-
- createActivity();
- }
-
- /**
- * Get a string array from the print spooler's resources.
- *
- * @param resourceName The name of the array resource
- * @return The localized string array
- *
- * @throws Exception If anything is unexpected
- */
- String[] getPrintSpoolerStringArray(String resourceName) throws Exception {
- PackageManager pm = getActivity().getPackageManager();
- Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
- int id = printSpoolerRes.getIdentifier(resourceName, "array", PRINTSPOOLER_PACKAGE);
- return printSpoolerRes.getStringArray(id);
- }
-
- /**
- * Get a string from the print spooler's resources.
- *
- * @param resourceName The name of the string resource
- * @return The localized string
- *
- * @throws Exception If anything is unexpected
- */
- String getPrintSpoolerString(String resourceName) throws Exception {
- PackageManager pm = getActivity().getPackageManager();
- Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
- int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
- return printSpoolerRes.getString(id);
- }
-
- /**
- * Get a string with one parameter from the print spooler's resources.
- *
- * @param resourceName The name of the string resource
- * @return The localized string
- *
- * @throws Exception If anything is unexpected
- */
- String getPrintSpoolerStringOneParam(String resourceName, Object p)
- throws Exception {
- PackageManager pm = getActivity().getPackageManager();
- Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
- int id = printSpoolerRes.getIdentifier(resourceName, "string", PRINTSPOOLER_PACKAGE);
- return printSpoolerRes.getString(id, p);
- }
-
- /**
- * Get the default media size for the current locale.
- *
- * @return The default media size for the current locale
- *
- * @throws Exception If anything is unexpected
- */
- PrintAttributes.MediaSize getDefaultMediaSize() throws Exception {
- PackageManager pm = getActivity().getPackageManager();
- Resources printSpoolerRes = pm.getResourcesForApplication(PRINTSPOOLER_PACKAGE);
- int defaultMediaSizeResId = printSpoolerRes.getIdentifier("mediasize_default", "string",
- PRINTSPOOLER_PACKAGE);
- String defaultMediaSizeName = printSpoolerRes.getString(defaultMediaSizeResId);
-
- switch (defaultMediaSizeName) {
- case "NA_LETTER":
- return PrintAttributes.MediaSize.NA_LETTER;
- case "JIS_B5":
- return PrintAttributes.MediaSize.JIS_B5;
- case "ISO_A4":
- return PrintAttributes.MediaSize.ISO_A4;
- default:
- throw new Exception("Unknown default media size " + defaultMediaSizeName);
- }
- }
-
- /**
- * Annotation used to signal that a test does not need an activity.
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- @interface NoActivity { }
-
- /**
- * Rule that handles the {@link NoActivity} annotation.
- */
- private static class ShouldStartActivity implements TestRule {
- boolean noActivity;
-
- @Override
- public Statement apply(Statement base, org.junit.runner.Description description) {
- for (Annotation annotation : description.getAnnotations()) {
- if (annotation instanceof NoActivity) {
- noActivity = true;
- break;
- }
- }
-
- return base;
- }
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/ClassParametersTest.java b/tests/tests/print/src/android/print/cts/ClassParametersTest.java
index e0c41ea..a5e5d49 100644
--- a/tests/tests/print/src/android/print/cts/ClassParametersTest.java
+++ b/tests/tests/print/src/android/print/cts/ClassParametersTest.java
@@ -16,17 +16,21 @@
package android.print.cts;
+import static android.print.test.Utils.assertException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
import android.print.PrintDocumentInfo;
import android.support.test.runner.AndroidJUnit4;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
-
/**
* Test that the print attributes can be constructed correctly. This does not test that the
* attributes have the desired effect when send to the print framework.
diff --git a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
index eb9806e..3ea14f6 100644
--- a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
+++ b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
@@ -16,6 +16,8 @@
package android.print.cts;
+import static android.print.test.Utils.eventually;
+
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
@@ -30,32 +32,29 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.CustomPrintOptionsActivity;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.CustomPrintOptionsActivity;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
import android.util.Log;
+
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeoutException;
-import static android.print.cts.Utils.eventually;
-
/**
* This test verifies changes to the printer capabilities are applied correctly.
*/
diff --git a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
index 37eab57..da7e82d 100644
--- a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
+++ b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
@@ -16,6 +16,14 @@
package android.print.cts;
+import static android.print.test.Utils.eventually;
+import static android.print.test.Utils.getPrintJob;
+import static android.print.test.Utils.getPrintManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintJob;
@@ -23,23 +31,22 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.support.annotation.NonNull;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
-
/**
* Test interface from the application to the print service.
*/
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
index f128bab..c4fd77e 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustAndVerify.java
@@ -36,12 +36,13 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
import android.print.pdf.PrintedPdfDocument;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.support.annotation.NonNull;
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index 0364624..c1abafc 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -16,7 +16,8 @@
package android.print.cts;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
@@ -35,15 +36,16 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.printservice.PrintService;
-
import android.support.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tests/tests/print/src/android/print/cts/PrintAttributesTest.java b/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
index e6bc164..7661ff9 100644
--- a/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintAttributesTest.java
@@ -16,6 +16,8 @@
package android.print.cts;
+import static org.junit.Assert.assertEquals;
+
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
@@ -29,15 +31,16 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
-
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,8 +49,6 @@
import java.util.Arrays;
import java.util.List;
-import static org.junit.Assert.*;
-
/**
* Test that the print attributes are correctly propagated through the print framework
*/
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java b/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
deleted file mode 100644
index c3e6e96..0000000
--- a/tests/tests/print/src/android/print/cts/PrintDocumentActivity.java
+++ /dev/null
@@ -1,53 +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 android.print.cts;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.WindowManager;
-
-public class PrintDocumentActivity extends Activity {
- private static final String LOG_TAG = "PrintDocumentActivity";
- int mTestId;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mTestId = getIntent().getIntExtra(BasePrintTest.TEST_ID, -1);
- Log.d(LOG_TAG, "onCreate() " + this + " for " + mTestId);
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-
- KeyguardManager km = getSystemService(KeyguardManager.class);
- if (km != null) {
- km.requestDismissKeyguard(this, null);
- }
-
- BasePrintTest.onActivityCreateCalled(mTestId, this);
- }
-
- @Override
- protected void onDestroy() {
- Log.d(LOG_TAG, "onDestroy() " + this);
- BasePrintTest.onActivityDestroyCalled(mTestId);
- super.onDestroy();
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
index 38ba4dd..4851ba9 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -36,11 +36,12 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.support.test.runner.AndroidJUnit4;
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
index a9d03c9..63e83e3 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentInfoTest.java
@@ -16,6 +16,11 @@
package android.print.cts;
+import static android.print.test.Utils.eventually;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
@@ -29,14 +34,16 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.support.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,16 +52,12 @@
import java.util.ArrayList;
import java.util.List;
-import static android.print.cts.Utils.eventually;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
/**
* This test verifies that the system respects the {@link PrintDocumentAdapter}
* contract and invokes all callbacks as expected.
*/
@RunWith(AndroidJUnit4.class)
-public class PrintDocumentInfoTest extends android.print.cts.BasePrintTest {
+public class PrintDocumentInfoTest extends BasePrintTest {
private static boolean sIsDefaultPrinterSet;
@Before
diff --git a/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java b/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
index d5edcb6..75a7847 100644
--- a/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintJobStateTransitionsTest.java
@@ -16,7 +16,7 @@
package android.print.cts;
-import static android.print.cts.Utils.eventually;
+import static android.print.test.Utils.eventually;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -30,11 +30,12 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.util.Log;
diff --git a/tests/tests/print/src/android/print/cts/PrintJobTest.java b/tests/tests/print/src/android/print/cts/PrintJobTest.java
index f3081fb..1fcfc17 100644
--- a/tests/tests/print/src/android/print/cts/PrintJobTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintJobTest.java
@@ -16,6 +16,14 @@
package android.print.cts;
+import static android.print.test.Utils.eventually;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
@@ -26,23 +34,22 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.CustomPrintOptionsActivity;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.CustomPrintOptionsActivity;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.support.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import static android.print.cts.Utils.eventually;
-import static org.junit.Assert.*;
-
/**
* Tests all possible states of print jobs.
*/
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index 7be5fc1..b5a23e2 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -16,6 +16,16 @@
package android.print.cts;
+import static android.print.test.Utils.assertException;
+import static android.print.test.Utils.eventually;
+import static android.print.test.Utils.getPrintJob;
+import static android.print.test.Utils.runOnMainThread;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -38,13 +48,14 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.InfoActivity;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.InfoActivity;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.CustomPrinterIconCallback;
import android.printservice.PrintJob;
import android.printservice.PrintService;
@@ -52,15 +63,13 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
-
/**
* Test the interface from a print service to the print manager
*/
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
index 9b277e9..251b371 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
@@ -16,6 +16,8 @@
package android.print.cts;
+import static android.print.test.Utils.runOnMainThread;
+
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
@@ -29,14 +31,14 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiSelector;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.util.Log;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,8 +49,6 @@
import java.util.List;
import java.util.concurrent.TimeoutException;
-import static android.print.cts.Utils.runOnMainThread;
-
/**
* This test verifies changes to the printer capabilities are applied correctly.
*/
@@ -154,23 +154,6 @@
PrinterInfo.STATUS_UNAVAILABLE)));
}
- /**
- * Wait until the message is shown that indicates that a printer is unavilable.
- *
- * @throws Exception If anything was unexpected.
- */
- private void waitForPrinterUnavailable() throws Exception {
- final String PRINTER_UNAVAILABLE_MSG =
- getPrintSpoolerString("print_error_printer_unavailable");
-
- UiObject message = getUiDevice().findObject(new UiSelector().resourceId(
- "com.android.printspooler:id/message"));
- if (!message.getText().equals(PRINTER_UNAVAILABLE_MSG)) {
- throw new Exception("Wrong message: " + message.getText() + " instead of " +
- PRINTER_UNAVAILABLE_MSG);
- }
- }
-
@Before
public void setUpPrinting() throws Exception {
// Create the mSession[0] callbacks that we will be checking.
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
index 3ea03af..3954948 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
@@ -16,6 +16,11 @@
package android.print.cts;
+import static android.print.test.Utils.assertException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
@@ -29,12 +34,14 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.support.test.runner.AndroidJUnit4;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,9 +49,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
-import static android.print.cts.Utils.assertException;
-import static org.junit.Assert.*;
-
/**
* This test verifies changes to the printer capabilities are applied correctly.
*/
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index 499d21e..a21ed18 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -16,8 +16,13 @@
package android.print.cts;
-import static android.print.cts.Utils.*;
-import static org.junit.Assert.*;
+import static android.print.test.Utils.eventually;
+import static android.print.test.Utils.runOnMainThread;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.inOrder;
import android.print.PrintAttributes;
@@ -28,15 +33,19 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.printservice.PrinterDiscoverySession;
-
+import android.support.annotation.NonNull;
import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiSelector;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,10 +62,7 @@
*/
@RunWith(AndroidJUnit4.class)
public class PrinterDiscoverySessionLifecycleTest extends BasePrintTest {
- private static final String FIRST_PRINTER_NAME = "First printer";
- private static final String SECOND_PRINTER_NAME = "Second printer";
-
- private static final String FIRST_PRINTER_LOCAL_ID= "first_printer";
+ private static final String FIRST_PRINTER_LOCAL_ID = "first_printer";
private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
private static StubbablePrinterDiscoverySession sSession;
@@ -66,6 +72,172 @@
clearPrintSpoolerData();
}
+ /**
+ * Add a printer to {@#sSession}.
+ *
+ * @param localId The id of the printer to add
+ * @param hasCapabilities If the printer has capabilities
+ */
+ private void addPrinter(@NonNull String localId, boolean hasCapabilities) {
+ // Add the first printer.
+ PrinterId firstPrinterId = sSession.getService().generatePrinterId(
+ localId);
+
+ PrinterInfo.Builder printer = new PrinterInfo.Builder(firstPrinterId,
+ localId, PrinterInfo.STATUS_IDLE);
+
+ if (hasCapabilities) {
+ printer.setCapabilities(new PrinterCapabilitiesInfo.Builder(firstPrinterId)
+ .setMinMargins(new Margins(200, 200, 200, 200))
+ .addMediaSize(MediaSize.ISO_A0, true)
+ .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .build());
+ }
+
+ sSession.addPrinters(Collections.singletonList(printer.build()));
+ }
+
+ /**
+ * Make {@code localPrinterId} the default printer. This requires a full print workflow.
+ *
+ * As a side-effect also approved the print service.
+ *
+ * @param localPrinterId The printer to make default
+ */
+ private void makeDefaultPrinter(String localPrinterId) throws Throwable {
+ PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
+
+ print(adapter);
+ waitForWriteAdapterCallback(1);
+
+ runOnMainThread(() -> addPrinter(localPrinterId, true));
+ selectPrinter(localPrinterId);
+ waitForWriteAdapterCallback(2);
+
+ clickPrintButton();
+ answerPrintServicesWarning(true);
+
+ waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+ resetCounters();
+ }
+
+ /**
+ * Select a printer in the all printers activity
+ *
+ * @param printerName The name of the printer to select
+ */
+ private void selectInAllPrintersActivity(@NonNull String printerName) throws Exception {
+ while (true) {
+ UiObject printerItem = getUiDevice().findObject(
+ new UiSelector().text(printerName));
+
+ if (printerItem.isEnabled()) {
+ printerItem.click();
+ break;
+ } else {
+ Thread.sleep(100);
+ }
+ }
+ }
+
+ @Test
+ public void defaultPrinterBecomesAvailableWhileInBackground() throws Throwable {
+ // Create the session callbacks that we will be checking.
+ final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
+ createMockPrinterDiscoverySessionCallbacks(invocation -> {
+ sSession =
+ ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
+
+ onPrinterDiscoverySessionCreateCalled();
+ return null;
+ }, null, null, null, null, null, invocation -> {
+ onPrinterDiscoverySessionDestroyCalled();
+ return null;
+ });
+
+ // Create the service callbacks for the first print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ invocation -> firstSessionCallbacks, null, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ makeDefaultPrinter(FIRST_PRINTER_LOCAL_ID);
+
+ PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
+ print(adapter);
+ waitForPrinterDiscoverySessionCreateCallbackCalled();
+
+ waitForPrinterUnavailable();
+
+ selectPrinter("All printers…");
+ // Let all printers activity start
+ Thread.sleep(500);
+
+ // Add printer
+ runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, true));
+
+ // Select printer once available (this returns to main print activity)
+ selectInAllPrintersActivity(FIRST_PRINTER_LOCAL_ID);
+
+ // Wait for preview to load and finish print
+ waitForWriteAdapterCallback(1);
+ clickPrintButton();
+ waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+ }
+
+ @Test
+ public void defaultPrinterBecomesUsableWhileInBackground() throws Throwable {
+ // Create the session callbacks that we will be checking.
+ final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
+ createMockPrinterDiscoverySessionCallbacks(invocation -> {
+ sSession =
+ ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
+
+ onPrinterDiscoverySessionCreateCalled();
+ return null;
+ }, null, null, null, null, null, invocation -> {
+ onPrinterDiscoverySessionDestroyCalled();
+ return null;
+ });
+
+ // Create the service callbacks for the first print service.
+ PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
+ invocation -> firstSessionCallbacks, null, null);
+
+ // Configure the print services.
+ FirstPrintService.setCallbacks(firstServiceCallbacks);
+ SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
+
+ makeDefaultPrinter(FIRST_PRINTER_LOCAL_ID);
+
+ PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
+ print(adapter);
+ waitForPrinterDiscoverySessionCreateCallbackCalled();
+
+ // Add printer but do not enable it (capabilities == null)
+ runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, false));
+ waitForPrinterUnavailable();
+
+ selectPrinter("All printers…");
+ // Let all printers activity start
+ Thread.sleep(500);
+
+ // Enable printer
+ runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, true));
+
+ // Select printer once available (this returns to main print activity)
+ selectInAllPrintersActivity(FIRST_PRINTER_LOCAL_ID);
+
+ // Wait for preview to load and finish print
+ waitForWriteAdapterCallback(1);
+ clickPrintButton();
+ waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
+ }
+
@Test
public void normalLifecycle() throws Throwable {
// Create the session callbacks that we will be checking.
@@ -99,7 +271,7 @@
runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
// Select the first printer.
- selectPrinter(FIRST_PRINTER_NAME);
+ selectPrinter(FIRST_PRINTER_LOCAL_ID);
eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -111,7 +283,7 @@
// Select the second printer (same capabilities as the other
// one so no layout should happen).
- selectPrinter(SECOND_PRINTER_NAME);
+ selectPrinter(SECOND_PRINTER_LOCAL_ID);
eventually(() -> runOnMainThread(() -> assertEquals(SECOND_PRINTER_LOCAL_ID,
sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -205,7 +377,7 @@
runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
// Select the first printer.
- selectPrinter(FIRST_PRINTER_NAME);
+ selectPrinter(FIRST_PRINTER_LOCAL_ID);
eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -295,7 +467,7 @@
runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
// Select the first printer.
- selectPrinter(FIRST_PRINTER_NAME);
+ selectPrinter(FIRST_PRINTER_LOCAL_ID);
eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
sSession.getTrackedPrinters().get(0).getLocalId())));
@@ -491,27 +663,9 @@
assertTrue(sSession.isPrinterDiscoveryStarted());
- if (sSession.getPrinters().isEmpty()) {
- List<PrinterInfo> printers = new ArrayList<>();
+ addPrinter(FIRST_PRINTER_LOCAL_ID, false);
+ addPrinter(SECOND_PRINTER_LOCAL_ID, false);
- // Add the first printer.
- PrinterId firstPrinterId = sSession.getService().generatePrinterId(
- FIRST_PRINTER_LOCAL_ID);
- PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
- FIRST_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
- .build();
- printers.add(firstPrinter);
-
- // Add the first printer.
- PrinterId secondPrinterId = sSession.getService().generatePrinterId(
- SECOND_PRINTER_LOCAL_ID);
- PrinterInfo secondPrinter = new PrinterInfo.Builder(secondPrinterId,
- SECOND_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
- .build();
- printers.add(secondPrinter);
-
- sSession.addPrinters(printers);
- }
return null;
}, invocation -> {
assertFalse(sSession.isPrinterDiscoveryStarted());
diff --git a/tests/tests/print/src/android/print/cts/PrinterInfoTest.java b/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
index 9c5c6ab..6a65e00 100644
--- a/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
@@ -27,11 +27,12 @@
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.print.cts.services.FirstPrintService;
-import android.print.cts.services.PrintServiceCallbacks;
-import android.print.cts.services.PrinterDiscoverySessionCallbacks;
-import android.print.cts.services.SecondPrintService;
-import android.print.cts.services.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.SecondPrintService;
+import android.print.test.services.StubbablePrinterDiscoverySession;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
diff --git a/tests/tests/print/src/android/print/cts/Utils.java b/tests/tests/print/src/android/print/cts/Utils.java
deleted file mode 100644
index 93f5632..0000000
--- a/tests/tests/print/src/android/print/cts/Utils.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.print.cts;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.print.PrintJob;
-import android.print.PrintManager;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-/**
- * Utilities for print tests
- */
-public class Utils {
- private static final String LOG_TAG = "Utils";
-
- /**
- * A {@link Runnable} that can throw an {@link Throwable}.
- */
- public interface Invokable {
- void run() throws Throwable;
- }
-
- /**
- * Run a {@link Invokable} and expect and {@link Throwable} of a certain type.
- *
- * @param r The {@link Invokable} to run
- * @param expectedClass The expected {@link Throwable} type
- */
- public static void assertException(@NonNull Invokable r,
- @NonNull Class<? extends Throwable> expectedClass) throws Throwable {
- try {
- r.run();
- } catch (Throwable e) {
- if (e.getClass().isAssignableFrom(expectedClass)) {
- return;
- } else {
- Log.e(LOG_TAG, "Expected: " + expectedClass.getName() + ", got: "
- + e.getClass().getName());
- throw e;
- }
- }
-
- throw new AssertionError("No throwable thrown");
- }
-
- /**
- * Run a {@link Invokable} on the main thread and forward the {@link Throwable} if one was
- * thrown.
- *
- * @param r The {@link Invokable} to run
- *
- * @throws Throwable If the {@link Runnable} caused an issue
- */
- static void runOnMainThread(@NonNull final Invokable r) throws Throwable {
- final Object synchronizer = new Object();
- final Throwable[] thrown = new Throwable[1];
-
- synchronized (synchronizer) {
- (new Handler(Looper.getMainLooper())).post(() -> {
- synchronized (synchronizer) {
- try {
- r.run();
- } catch (Throwable t) {
- thrown[0] = t;
- }
-
- synchronizer.notify();
- }
- });
-
- synchronizer.wait();
- }
-
- if (thrown[0] != null) {
- throw thrown[0];
- }
- }
-
- /**
- * Make sure that a {@link Invokable} eventually finishes without throwing a {@link Throwable}.
- *
- * @param r The {@link Invokable} to run.
- */
- public static void eventually(@NonNull Invokable r) throws Throwable {
- long start = System.currentTimeMillis();
-
- while (true) {
- try {
- r.run();
- break;
- } catch (Throwable e) {
- if (System.currentTimeMillis() - start < BasePrintTest.OPERATION_TIMEOUT_MILLIS) {
- Log.e(LOG_TAG, "Ignoring exception", e);
-
- try {
- Thread.sleep(500);
- } catch (InterruptedException e1) {
- Log.e(LOG_TAG, "Interrupted", e);
- }
- } else {
- throw e;
- }
- }
- }
- }
-
- /**
- * @param name Name of print job
- *
- * @return The print job for the name
- *
- * @throws Exception If print job could not be found
- */
- static @NonNull PrintJob getPrintJob(@NonNull PrintManager pm, @NonNull String name)
- throws Exception {
- for (android.print.PrintJob job : pm.getPrintJobs()) {
- if (job.getInfo().getLabel().equals(name)) {
- return job;
- }
- }
-
- throw new Exception("Print job " + name + " not found in " + pm.getPrintJobs());
- }
-
- /**
- * @return The print manager
- */
- static @NonNull PrintManager getPrintManager(@NonNull Context context) {
- return (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java b/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java
deleted file mode 100644
index c72d6f9..0000000
--- a/tests/tests/print/src/android/print/cts/services/AddPrintersActivity.java
+++ /dev/null
@@ -1,28 +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 android.print.cts.services;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class AddPrintersActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java b/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java
deleted file mode 100644
index cf52ece..0000000
--- a/tests/tests/print/src/android/print/cts/services/CustomPrintOptionsActivity.java
+++ /dev/null
@@ -1,76 +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 android.print.cts.services;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.print.PrintJobInfo;
-import android.print.PrinterInfo;
-import android.printservice.PrintService;
-
-/**
- * Custom print options activity for both print services
- */
-public class CustomPrintOptionsActivity extends Activity {
- /** Lock for {@link #sCallback} */
- private static final Object sLock = new Object();
-
- /** Currently registered callback for _both_ first and second print service. */
- private static CustomPrintOptionsCallback sCallback = null;
-
- /**
- * Set a new callback called when the custom options activity is launched.
- *
- * @param callback The new callback or null, if the callback should be unregistered.
- */
- public static void setCallBack(CustomPrintOptionsCallback callback) {
- synchronized (sLock) {
- sCallback = callback;
- }
- }
-
- /**
- * Callback executed for this activity. Set via {@link #setCallBack}.
- */
- public interface CustomPrintOptionsCallback {
- PrintJobInfo executeCustomPrintOptionsActivity(PrintJobInfo printJob,
- PrinterInfo printer);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent result = new Intent();
-
- synchronized (sLock) {
- if (sCallback != null) {
- PrintJobInfo printJobInfo = getIntent().getParcelableExtra(
- PrintService.EXTRA_PRINT_JOB_INFO);
- PrinterInfo printerInfo = getIntent().getParcelableExtra(
- "android.intent.extra.print.EXTRA_PRINTER_INFO");
-
- result.putExtra(PrintService.EXTRA_PRINT_JOB_INFO,
- sCallback.executeCustomPrintOptionsActivity(printJobInfo, printerInfo));
- }
- }
-
- setResult(Activity.RESULT_OK, result);
- finish();
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/FirstPrintService.java b/tests/tests/print/src/android/print/cts/services/FirstPrintService.java
deleted file mode 100644
index a234de4..0000000
--- a/tests/tests/print/src/android/print/cts/services/FirstPrintService.java
+++ /dev/null
@@ -1,40 +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 android.print.cts.services;
-
-public class FirstPrintService extends StubbablePrintService {
-
- private static final Object sLock = new Object();
-
- private static PrintServiceCallbacks sCallbacks;
-
- public static void setCallbacks(PrintServiceCallbacks callbacks) {
- synchronized (sLock) {
- sCallbacks = callbacks;
- }
- }
-
- @Override
- protected PrintServiceCallbacks getCallbacks() {
- synchronized (sLock) {
- if (sCallbacks != null) {
- sCallbacks.setService(this);
- }
- return sCallbacks;
- }
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/InfoActivity.java b/tests/tests/print/src/android/print/cts/services/InfoActivity.java
deleted file mode 100644
index 22c1bb5..0000000
--- a/tests/tests/print/src/android/print/cts/services/InfoActivity.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.print.cts.services;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.Observable;
-
-public class InfoActivity extends Activity {
- public interface Observer {
- void onCreate(Activity createdActivity);
- }
-
- private static final ArrayList<Observer> sObservers = new ArrayList<>();
-
- public static void addObserver(Observer observer) {
- synchronized (sObservers) {
- sObservers.add(observer);
- }
- }
-
- public static void clearObservers() {
- synchronized (sObservers) {
- sObservers.clear();
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- synchronized (sObservers) {
- ArrayList<Observer> observers = (ArrayList<Observer>) sObservers.clone();
-
- for (Observer observer : observers) {
- observer.onCreate(this);
- }
- }
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java b/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java
deleted file mode 100644
index 749e8a9..0000000
--- a/tests/tests/print/src/android/print/cts/services/PrintServiceCallbacks.java
+++ /dev/null
@@ -1,38 +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 android.print.cts.services;
-
-import android.printservice.PrintJob;
-
-public abstract class PrintServiceCallbacks {
-
- private StubbablePrintService mService;
-
- public StubbablePrintService getService() {
- return mService;
- }
-
- public void setService(StubbablePrintService service) {
- mService = service;
- }
-
- public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
-
- public abstract void onRequestCancelPrintJob(PrintJob printJob);
-
- public abstract void onPrintJobQueued(PrintJob printJob);
-}
diff --git a/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java b/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java
deleted file mode 100644
index ebddda1..0000000
--- a/tests/tests/print/src/android/print/cts/services/PrinterDiscoverySessionCallbacks.java
+++ /dev/null
@@ -1,51 +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 android.print.cts.services;
-
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-
-import java.util.List;
-
-public abstract class PrinterDiscoverySessionCallbacks {
-
- private StubbablePrinterDiscoverySession mSession;
-
- public void setSession(StubbablePrinterDiscoverySession session) {
- mSession = session;
- }
-
- public StubbablePrinterDiscoverySession getSession() {
- return mSession;
- }
-
- public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
-
- public abstract void onStopPrinterDiscovery();
-
- public abstract void onValidatePrinters(List<PrinterId> printerIds);
-
- public abstract void onStartPrinterStateTracking(PrinterId printerId);
-
- public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
- CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
-
- public abstract void onStopPrinterStateTracking(PrinterId printerId);
-
- public abstract void onDestroy();
-}
diff --git a/tests/tests/print/src/android/print/cts/services/SecondPrintService.java b/tests/tests/print/src/android/print/cts/services/SecondPrintService.java
deleted file mode 100644
index 1029a8e..0000000
--- a/tests/tests/print/src/android/print/cts/services/SecondPrintService.java
+++ /dev/null
@@ -1,40 +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 android.print.cts.services;
-
-public class SecondPrintService extends StubbablePrintService {
-
- private static final Object sLock = new Object();
-
- private static PrintServiceCallbacks sCallbacks;
-
- public static void setCallbacks(PrintServiceCallbacks callbacks) {
- synchronized (sLock) {
- sCallbacks = callbacks;
- }
- }
-
- @Override
- protected PrintServiceCallbacks getCallbacks() {
- synchronized (sLock) {
- if (sCallbacks != null) {
- sCallbacks.setService(this);
- }
- return sCallbacks;
- }
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/SettingsActivity.java b/tests/tests/print/src/android/print/cts/services/SettingsActivity.java
deleted file mode 100644
index eb23574..0000000
--- a/tests/tests/print/src/android/print/cts/services/SettingsActivity.java
+++ /dev/null
@@ -1,28 +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 android.print.cts.services;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class SettingsActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-}
diff --git a/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java b/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java
deleted file mode 100644
index 8c3b89b..0000000
--- a/tests/tests/print/src/android/print/cts/services/StubbablePrintService.java
+++ /dev/null
@@ -1,64 +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 android.print.cts.services;
-
-import android.content.Context;
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-import java.util.List;
-
-public abstract class StubbablePrintService extends PrintService {
-
- @Override
- public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
- PrintServiceCallbacks callbacks = getCallbacks();
- if (callbacks != null) {
- return new StubbablePrinterDiscoverySession(this,
- getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
- }
- return null;
- }
-
- @Override
- public void onRequestCancelPrintJob(PrintJob printJob) {
- PrintServiceCallbacks callbacks = getCallbacks();
- if (callbacks != null) {
- callbacks.onRequestCancelPrintJob(printJob);
- }
- }
-
- @Override
- public void onPrintJobQueued(PrintJob printJob) {
- PrintServiceCallbacks callbacks = getCallbacks();
- if (callbacks != null) {
- callbacks.onPrintJobQueued(printJob);
- }
- }
-
- protected abstract PrintServiceCallbacks getCallbacks();
-
- public void callAttachBaseContext(Context base) {
- attachBaseContext(base);
- }
-
- public List<PrintJob> callGetActivePrintJobs() {
- return getActivePrintJobs();
- }
-
-}
diff --git a/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java b/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java
deleted file mode 100644
index e0ec1c1..0000000
--- a/tests/tests/print/src/android/print/cts/services/StubbablePrinterDiscoverySession.java
+++ /dev/null
@@ -1,95 +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 android.print.cts.services;
-
-import android.support.annotation.NonNull;
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-import java.util.List;
-
-public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
- private final PrintService mService;
- private final PrinterDiscoverySessionCallbacks mCallbacks;
-
- public StubbablePrinterDiscoverySession(PrintService service,
- PrinterDiscoverySessionCallbacks callbacks) {
- mService = service;
- mCallbacks = callbacks;
- if (mCallbacks != null) {
- mCallbacks.setSession(this);
- }
- }
-
- public PrintService getService() {
- return mService;
- }
-
- @Override
- public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
- if (mCallbacks != null) {
- mCallbacks.onStartPrinterDiscovery(priorityList);
- }
- }
-
- @Override
- public void onStopPrinterDiscovery() {
- if (mCallbacks != null) {
- mCallbacks.onStopPrinterDiscovery();
- }
- }
-
- @Override
- public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
- if (mCallbacks != null) {
- mCallbacks.onValidatePrinters(printerIds);
- }
- }
-
- @Override
- public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
- if (mCallbacks != null) {
- mCallbacks.onStartPrinterStateTracking(printerId);
- }
- }
-
- @Override
- public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull CustomPrinterIconCallback callback) {
- if (mCallbacks != null) {
- mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
- }
- }
-
- @Override
- public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
- if (mCallbacks != null) {
- mCallbacks.onStopPrinterStateTracking(printerId);
- }
- }
-
- @Override
- public void onDestroy() {
- if (mCallbacks != null) {
- mCallbacks.onDestroy();
- }
- }
-}
diff --git a/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java b/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java
index 344bcfc..4c2b369 100644
--- a/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java
+++ b/tests/tests/print/src/android/print/pdf/cts/PrintedPdfDocumentTest.java
@@ -16,6 +16,10 @@
package android.print.pdf.cts;
+import static android.print.test.Utils.assertException;
+
+import static org.junit.Assert.assertEquals;
+
import android.content.Context;
import android.graphics.Rect;
import android.graphics.pdf.PdfDocument;
@@ -23,13 +27,11 @@
import android.print.pdf.PrintedPdfDocument;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static android.print.cts.Utils.assertException;
-import static org.junit.Assert.assertEquals;
-
/**
* Tests {@link PrintedPdfDocument}. This class is a subclass of {@link PdfDocument}, hence only the
* overridden methods are tested.
diff --git a/tests/tests/proto/AndroidTest.xml b/tests/tests/proto/AndroidTest.xml
index 025fc26..05df543 100644
--- a/tests/tests/proto/AndroidTest.xml
+++ b/tests/tests/proto/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Proto Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="metrics" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
index fb23607..8178b46 100644
--- a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamObjectTest.java
@@ -79,7 +79,7 @@
final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
long token = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(2,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
@@ -106,7 +106,7 @@
final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
long token = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(5000,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'\u3110');
@@ -138,7 +138,7 @@
'a');
long token = po.startObject(ProtoOutputStream.makeFieldId(2,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(3,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
@@ -177,7 +177,7 @@
final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
long token = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token);
Assert.assertArrayEquals(new byte[0], po.getBytes());
@@ -199,11 +199,11 @@
final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token2 = po.startObject(ProtoOutputStream.makeFieldId(2,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token3 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token3);
po.endObject(token2);
po.endObject(token1);
@@ -231,7 +231,7 @@
'a');
long token = po.startObject(ProtoOutputStream.makeFieldId(2,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token);
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
@@ -263,11 +263,11 @@
long token;
token = po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endRepeatedObject(token);
token = po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endRepeatedObject(token);
Assert.assertArrayEquals(new byte[] {
@@ -299,7 +299,7 @@
'x');
long token = po.startObject(ProtoOutputStream.makeFieldId(2,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(3,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'y');
@@ -308,7 +308,7 @@
"abcdefghijkl");
long tokenEmpty = po.startObject(ProtoOutputStream.makeFieldId(500,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(tokenEmpty);
po.endObject(token);
@@ -349,19 +349,19 @@
final ProtoOutputStream po = new ProtoOutputStream(chunkSize);
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(2,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'a');
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(6,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'c');
@@ -394,13 +394,13 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(2,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'a');
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
@@ -422,10 +422,10 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token2);
try {
@@ -444,13 +444,13 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(2,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'a');
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
@@ -472,10 +472,10 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token2);
try {
@@ -494,13 +494,13 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(2,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'a');
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
@@ -521,10 +521,10 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
try {
po.endObject(token1);
@@ -542,20 +542,20 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(2,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'a');
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
po.endObject(token2);
long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeUInt32(ProtoOutputStream.makeFieldId(4,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_UINT32),
'b');
@@ -576,14 +576,14 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token2);
long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
try {
po.endObject(token2);
@@ -601,14 +601,14 @@
final ProtoOutputStream po = new ProtoOutputStream();
long token1 = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
long token2 = po.startObject(ProtoOutputStream.makeFieldId(3,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.endObject(token2);
long token3 = po.startObject(ProtoOutputStream.makeFieldId(5,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
try {
po.endObject(token2);
@@ -638,17 +638,17 @@
all.nestedField.nested.nested.data = 3;
final long token1 = po.startObject(ProtoOutputStream.makeFieldId(170,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeInt32(ProtoOutputStream.makeFieldId(10001,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
1);
final long token2 = po.startObject(ProtoOutputStream.makeFieldId(10002,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeInt32(ProtoOutputStream.makeFieldId(10001,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
2);
final long token3 = po.startObject(ProtoOutputStream.makeFieldId(10002,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeInt32(ProtoOutputStream.makeFieldId(10001,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
3);
@@ -680,17 +680,17 @@
all.nestedFieldRepeated[i].nested.nested.data = 3;
final long token1 = po.startObject(ProtoOutputStream.makeFieldId(171,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeInt32(ProtoOutputStream.makeFieldId(10001,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
1);
final long token2 = po.startObject(ProtoOutputStream.makeFieldId(10002,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeInt32(ProtoOutputStream.makeFieldId(10001,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
2);
final long token3 = po.startObject(ProtoOutputStream.makeFieldId(10002,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
po.writeInt32(ProtoOutputStream.makeFieldId(10001,
ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_INT32),
3);
@@ -724,7 +724,7 @@
try {
final ProtoOutputStream po = new ProtoOutputStream();
po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
} catch (IllegalArgumentException ex) {
// good
}
@@ -744,7 +744,7 @@
try {
final ProtoOutputStream po = new ProtoOutputStream();
po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_PACKED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
} catch (IllegalArgumentException ex) {
// good
}
@@ -756,7 +756,7 @@
public void testMismatchedEndObject() {
final ProtoOutputStream po = new ProtoOutputStream();
final long token = po.startObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE));
try {
po.endRepeatedObject(token);
@@ -771,7 +771,7 @@
public void testMismatchedEndRepeatedObject() {
final ProtoOutputStream po = new ProtoOutputStream();
final long token = po.startRepeatedObject(ProtoOutputStream.makeFieldId(1,
- ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT));
+ ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE));
try {
po.endObject(token);
@@ -806,7 +806,7 @@
final ProtoOutputStream po = new ProtoOutputStream();
po.writeObject(ProtoOutputStream.makeFieldId(10,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT),
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE),
innerRaw);
final byte[] result = po.getBytes();
@@ -827,7 +827,7 @@
final ProtoOutputStream po = new ProtoOutputStream();
po.writeObject(ProtoOutputStream.makeFieldId(10,
- ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_OBJECT),
+ ProtoOutputStream.FIELD_COUNT_SINGLE | ProtoOutputStream.FIELD_TYPE_MESSAGE),
innerRaw);
final byte[] result = po.getBytes();
@@ -861,7 +861,7 @@
final ProtoOutputStream po = new ProtoOutputStream();
po.writeRepeatedObject(ProtoOutputStream.makeFieldId(10,
- ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT),
+ ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE),
innerRaw);
final byte[] result = po.getBytes();
@@ -882,7 +882,7 @@
final ProtoOutputStream po = new ProtoOutputStream();
po.writeRepeatedObject(ProtoOutputStream.makeFieldId(10,
- ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_OBJECT),
+ ProtoOutputStream.FIELD_COUNT_REPEATED | ProtoOutputStream.FIELD_TYPE_MESSAGE),
innerRaw);
Assert.assertArrayEquals(new byte[] {
diff --git a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java
index 3b38aba..c408d92 100644
--- a/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java
+++ b/tests/tests/proto/src/android/util/proto/cts/ProtoOutputStreamTagTest.java
@@ -92,6 +92,7 @@
// Try it in the provided name
try {
ProtoOutputStream.checkFieldId(42 | goodCount | badType, goodCount | fieldType);
+ fail("Should have thrown an exception.");
} catch (IllegalArgumentException ex) {
assertEquals("writeRepeated" + string
+ " called for field 42 which should be used"
@@ -102,6 +103,7 @@
// Try it in the expected name
try {
ProtoOutputStream.checkFieldId(43 | goodCount | fieldType, goodCount | badType);
+ fail("Should have thrown an exception.");
} catch (IllegalArgumentException ex) {
assertEquals("writeRepeated" + badTypeString
+ " called for field 43 which should be used"
@@ -126,6 +128,7 @@
// Try it in the provided name
try {
ProtoOutputStream.checkFieldId(44 | badCount | goodType, fieldCount | goodType);
+ fail("Should have thrown an exception.");
} catch (IllegalArgumentException ex) {
assertEquals("write" + string
+ "Fixed32 called for field 44 which should be used"
@@ -136,6 +139,7 @@
// Try it in the expected name
try {
ProtoOutputStream.checkFieldId(45 | fieldCount | goodType, badCount | goodType);
+ fail("Should have thrown an exception.");
} catch (IllegalArgumentException ex) {
String extraString = "";
if (fieldCount == ProtoOutputStream.FIELD_COUNT_PACKED) {
@@ -151,7 +155,7 @@
/**
* Validate one call to checkFieldId that is expected to throw.
*/
- public void assertCheckFieldIdThrows(long fieldId, long expectedFlags)
+ public void assertCheckFieldIdThrows(long fieldId, long expectedFlags)
throws Exception {
try {
ProtoOutputStream.checkFieldId(fieldId, expectedFlags);
diff --git a/tests/tests/provider/Android.mk b/tests/tests/provider/Android.mk
index d4b3e99..6effdfa 100644
--- a/tests/tests/provider/Android.mk
+++ b/tests/tests/provider/Android.mk
@@ -28,7 +28,7 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_JAVA_LIBRARIES := android.test.mock legacy-android-test telephony-common
+LOCAL_JAVA_LIBRARIES := android.test.mock android.test.base android.test.runner telephony-common
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v4 \
diff --git a/tests/tests/provider/AndroidTest.xml b/tests/tests/provider/AndroidTest.xml
index 57198a8..a8fe97d 100644
--- a/tests/tests/provider/AndroidTest.xml
+++ b/tests/tests/provider/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Provider test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
index 31afce9..df94bda 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
@@ -72,6 +72,8 @@
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.DATA_VERSION,
Data.IS_PRIMARY,
Data.IS_SUPER_PRIMARY,
@@ -286,6 +288,8 @@
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.DATA_VERSION,
Data.IS_PRIMARY,
Data.IS_SUPER_PRIMARY,
@@ -379,6 +383,8 @@
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.DATA_VERSION,
Data.IS_PRIMARY,
Data.IS_SUPER_PRIMARY,
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java b/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
index bdc973b..c0b4253 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
@@ -88,32 +88,46 @@
public void testVoicemailsTable() throws Exception {
final String[] VOICEMAILS_PROJECTION = new String[] {
- Voicemails._ID, Voicemails.NUMBER, Voicemails.DATE, Voicemails.DURATION,
- Voicemails.IS_READ, Voicemails.SOURCE_PACKAGE, Voicemails.SOURCE_DATA,
- Voicemails.HAS_CONTENT, Voicemails.MIME_TYPE, Voicemails.TRANSCRIPTION,
+ Voicemails._ID,
+ Voicemails.NUMBER,
+ Voicemails.DATE,
+ Voicemails.DURATION,
+ Voicemails.NEW,
+ Voicemails.IS_READ,
+ Voicemails.SOURCE_PACKAGE,
+ Voicemails.SOURCE_DATA,
+ Voicemails.HAS_CONTENT,
+ Voicemails.MIME_TYPE,
+ Voicemails.TRANSCRIPTION,
Voicemails.PHONE_ACCOUNT_COMPONENT_NAME,
- Voicemails.PHONE_ACCOUNT_ID, Voicemails.DIRTY, Voicemails.DELETED,
- Voicemails.LAST_MODIFIED, Voicemails.BACKED_UP, Voicemails.RESTORED,
- Voicemails.ARCHIVED, Voicemails.IS_OMTP_VOICEMAIL};
+ Voicemails.PHONE_ACCOUNT_ID,
+ Voicemails.DIRTY,
+ Voicemails.DELETED,
+ Voicemails.LAST_MODIFIED,
+ Voicemails.BACKED_UP,
+ Voicemails.RESTORED,
+ Voicemails.ARCHIVED,
+ Voicemails.IS_OMTP_VOICEMAIL};
final int ID_INDEX = 0;
final int NUMBER_INDEX = 1;
final int DATE_INDEX = 2;
final int DURATION_INDEX = 3;
- final int IS_READ_INDEX = 4;
- final int SOURCE_PACKAGE_INDEX = 5;
- final int SOURCE_DATA_INDEX = 6;
- final int HAS_CONTENT_INDEX = 7;
- final int MIME_TYPE_INDEX = 8;
- final int TRANSCRIPTION_INDEX= 9;
- final int PHONE_ACCOUNT_COMPONENT_NAME_INDEX = 10;
- final int PHONE_ACCOUNT_ID_INDEX = 11;
- final int DIRTY_INDEX = 12;
- final int DELETED_INDEX = 13;
- final int LAST_MODIFIED_INDEX = 14;
- final int BACKED_UP_INDEX = 15;
- final int RESTORED_INDEX = 16;
- final int ARCHIVED_INDEX = 17;
- final int IS_OMTP_VOICEMAIL_INDEX = 18;
+ final int NEW_INDEX = 4;
+ final int IS_READ_INDEX = 5;
+ final int SOURCE_PACKAGE_INDEX = 6;
+ final int SOURCE_DATA_INDEX = 7;
+ final int HAS_CONTENT_INDEX = 8;
+ final int MIME_TYPE_INDEX = 9;
+ final int TRANSCRIPTION_INDEX = 10;
+ final int PHONE_ACCOUNT_COMPONENT_NAME_INDEX = 11;
+ final int PHONE_ACCOUNT_ID_INDEX = 12;
+ final int DIRTY_INDEX = 13;
+ final int DELETED_INDEX = 14;
+ final int LAST_MODIFIED_INDEX = 15;
+ final int BACKED_UP_INDEX = 16;
+ final int RESTORED_INDEX = 17;
+ final int ARCHIVED_INDEX = 18;
+ final int IS_OMTP_VOICEMAIL_INDEX = 19;
String insertCallsNumber = "0123456789";
long insertCallsDuration = 120;
@@ -131,6 +145,7 @@
value.put(Voicemails.NUMBER, insertCallsNumber);
value.put(Voicemails.DATE, insertDate);
value.put(Voicemails.DURATION, insertCallsDuration);
+ value.put(Voicemails.NEW, 0);
// Source package is expected to be inserted by the provider, if not set.
value.put(Voicemails.SOURCE_DATA, insertSourceData);
value.put(Voicemails.MIME_TYPE, insertMimeType);
@@ -158,17 +173,18 @@
assertEquals(mSourcePackageName, cursor.getString(SOURCE_PACKAGE_INDEX));
assertEquals(insertSourceData, cursor.getString(SOURCE_DATA_INDEX));
assertEquals(insertMimeType, cursor.getString(MIME_TYPE_INDEX));
+ assertEquals(0, cursor.getInt(NEW_INDEX));
assertEquals(0, cursor.getInt(IS_READ_INDEX));
assertEquals(1, cursor.getInt(HAS_CONTENT_INDEX));
- assertEquals("foo",cursor.getString(TRANSCRIPTION_INDEX));
- assertEquals("com.foo",cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_INDEX));
- assertEquals("bar",cursor.getString(PHONE_ACCOUNT_ID_INDEX));
- assertEquals(0,cursor.getInt(DIRTY_INDEX));
- assertEquals(0,cursor.getInt(DELETED_INDEX));
- assertEquals(0,cursor.getInt(BACKED_UP_INDEX));
- assertEquals(0,cursor.getInt(RESTORED_INDEX));
- assertEquals(0,cursor.getInt(ARCHIVED_INDEX));
- assertEquals(0,cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
+ assertEquals("foo", cursor.getString(TRANSCRIPTION_INDEX));
+ assertEquals("com.foo", cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_INDEX));
+ assertEquals("bar", cursor.getString(PHONE_ACCOUNT_ID_INDEX));
+ assertEquals(0, cursor.getInt(DIRTY_INDEX));
+ assertEquals(0, cursor.getInt(DELETED_INDEX));
+ assertEquals(0, cursor.getInt(BACKED_UP_INDEX));
+ assertEquals(0, cursor.getInt(RESTORED_INDEX));
+ assertEquals(0, cursor.getInt(ARCHIVED_INDEX));
+ assertEquals(0, cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
int id = cursor.getInt(ID_INDEX);
assertEquals(id, Integer.parseInt(uri.getLastPathSegment()));
cursor.close();
@@ -179,6 +195,7 @@
value.put(Voicemails.DATE, updateDate);
value.put(Voicemails.DURATION, updateCallsDuration);
value.put(Voicemails.SOURCE_DATA, updateSourceData);
+ value.put(Voicemails.NEW, 1);
value.put(Voicemails.DIRTY, 1);
value.put(Voicemails.DELETED, 1);
value.put(Voicemails.BACKED_UP, 1);
@@ -196,12 +213,13 @@
assertEquals(updateDate, cursor.getLong(DATE_INDEX));
assertEquals(updateCallsDuration, cursor.getLong(DURATION_INDEX));
assertEquals(updateSourceData, cursor.getString(SOURCE_DATA_INDEX));
- assertEquals(1,cursor.getInt(DIRTY_INDEX));
- assertEquals(1,cursor.getInt(DELETED_INDEX));
- assertEquals(1,cursor.getInt(BACKED_UP_INDEX));
- assertEquals(1,cursor.getInt(RESTORED_INDEX));
- assertEquals(1,cursor.getInt(ARCHIVED_INDEX));
- assertEquals(1,cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
+ assertEquals(1, cursor.getInt(NEW_INDEX));
+ assertEquals(1, cursor.getInt(DIRTY_INDEX));
+ assertEquals(1, cursor.getInt(DELETED_INDEX));
+ assertEquals(1, cursor.getInt(BACKED_UP_INDEX));
+ assertEquals(1, cursor.getInt(RESTORED_INDEX));
+ assertEquals(1, cursor.getInt(ARCHIVED_INDEX));
+ assertEquals(1, cursor.getInt(IS_OMTP_VOICEMAIL_INDEX));
cursor.close();
// Test: delete
@@ -213,7 +231,7 @@
}
public void testForeignUpdate_dirty() throws Exception {
- if(!hasTelephony(getInstrumentation().getContext())){
+ if (!hasTelephony(getInstrumentation().getContext())) {
Log.d(TAG, "skipping test that requires telephony feature");
return;
}
@@ -234,8 +252,34 @@
}
}
+ public void testForeignUpdate_retainDirty_notDirty() throws Exception {
+ if (!hasTelephony(getInstrumentation().getContext())) {
+ Log.d(TAG, "skipping test that requires telephony feature");
+ return;
+ }
+ // only the default dialer has WRITE_VOICEMAIL permission, which can modify voicemails of
+ // a foreign source package.
+ setTestAsDefaultDialer();
+ ContentValues values = new ContentValues();
+ values.put(Voicemails.SOURCE_PACKAGE, FOREIGN_SOURCE);
+
+ Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(FOREIGN_SOURCE), values);
+
+ ContentValues newValues = new ContentValues();
+ newValues.put(Voicemails.TRANSCRIPTION, "foo");
+ newValues.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+ mVoicemailProvider.update(uri, newValues, null, null);
+
+ try (Cursor cursor = mVoicemailProvider
+ .query(uri, new String[] {Voicemails.DIRTY}, null, null, null)) {
+ cursor.moveToFirst();
+ assertEquals(0, cursor.getInt(0));
+ }
+ }
+
public void testForeignUpdate_explicitNotDirty() throws Exception {
- if(!hasTelephony(getInstrumentation().getContext())){
+ if (!hasTelephony(getInstrumentation().getContext())) {
Log.d(TAG, "skipping test that requires telephony feature");
return;
}
@@ -246,7 +290,7 @@
Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(FOREIGN_SOURCE), values);
ContentValues updateValues = new ContentValues();
- updateValues.put(Voicemails.DIRTY,0);
+ updateValues.put(Voicemails.DIRTY, 0);
mVoicemailProvider.update(uri, updateValues, null, null);
try (Cursor cursor = mVoicemailProvider
@@ -257,7 +301,7 @@
}
public void testForeignUpdate_null_dirty() throws Exception {
- if(!hasTelephony(getInstrumentation().getContext())){
+ if (!hasTelephony(getInstrumentation().getContext())) {
Log.d(TAG, "skipping test that requires telephony feature");
return;
}
@@ -279,7 +323,7 @@
}
public void testForeignUpdate_NotNormalized_normalized() throws Exception {
- if(!hasTelephony(getInstrumentation().getContext())){
+ if (!hasTelephony(getInstrumentation().getContext())) {
Log.d(TAG, "skipping test that requires telephony feature");
return;
}
@@ -303,7 +347,7 @@
public void testLocalUpdate_notDirty() throws Exception {
ContentValues values = new ContentValues();
- values.put(Voicemails.DIRTY,1);
+ values.put(Voicemails.DIRTY, 1);
Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(mSourcePackageName), values);
@@ -316,6 +360,25 @@
}
}
+ public void testLocalUpdate_retainDirty_dirty() throws Exception {
+
+ ContentValues values = new ContentValues();
+ values.put(Voicemails.DIRTY, 1);
+
+ Uri uri = mVoicemailProvider.insert(Voicemails.buildSourceUri(mSourcePackageName), values);
+
+ ContentValues newValues = new ContentValues();
+ newValues.put(Voicemails.TRANSCRIPTION, "foo");
+ newValues.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+ mVoicemailProvider.update(uri, newValues, null, null);
+
+ try (Cursor cursor = mVoicemailProvider
+ .query(uri, new String[] {Voicemails.DIRTY}, null, null, null)) {
+ cursor.moveToFirst();
+ assertEquals(cursor.getInt(0), 1);
+ }
+ }
// Data column should be automatically generated during insert.
public void testInsert_doesNotUpdateDataColumn() throws Exception {
@@ -351,7 +414,7 @@
private void assertDataNotEquals(String newFilePath) throws RemoteException {
// Make sure data value is not actually updated.
final Cursor cursor = mVoicemailProvider.query(mVoicemailContentUri,
- new String[]{Voicemails._DATA}, null, null, null);
+ new String[] {Voicemails._DATA}, null, null, null);
cursor.moveToNext();
final String data = cursor.getString(0);
assertFalse(data.equals(newFilePath));
@@ -494,10 +557,10 @@
packageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
}
- private void setTestAsDefaultDialer() throws Exception{
+ private void setTestAsDefaultDialer() throws Exception {
assertTrue(mPreviousDefaultDialer == null);
mPreviousDefaultDialer = getDefaultDialer(getInstrumentation());
- setDefaultDialer(getInstrumentation(),PACKAGE);
+ setDefaultDialer(getInstrumentation(), PACKAGE);
}
private static String setDefaultDialer(Instrumentation instrumentation, String packageName)
diff --git a/tests/tests/renderscript/Android.mk b/tests/tests/renderscript/Android.mk
index 1a8c549..5da2036 100644
--- a/tests/tests/renderscript/Android.mk
+++ b/tests/tests/renderscript/Android.mk
@@ -30,8 +30,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- xmp_toolkit \
- legacy-android-test
+ xmp_toolkit
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_JNI_SHARED_LIBRARIES := libcoremathtestcpp_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
diff --git a/tests/tests/renderscript/AndroidTest.xml b/tests/tests/renderscript/AndroidTest.xml
index 979b756..2788236 100644
--- a/tests/tests/renderscript/AndroidTest.xml
+++ b/tests/tests/renderscript/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Renderscript Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="renderscript" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
index 9b355b0..ab326ff 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
@@ -37,14 +37,14 @@
/**
* Test the orignal refocus code
*/
- public void testOriginalRefocus() {
+ public void testOriginalRefocus() throws IOException {
refocus(RenderScriptTask.script.f32, 95);
}
/**
* Test the new refocus code
*/
- public void testNewRefocus() {
+ public void testNewRefocus() throws IOException {
// The new implementation may run on a GPU using relaxed floating point
// mathematics. Hence more relaxed precision requirement.
refocus(RenderScriptTask.script.d1new, 45);
@@ -54,7 +54,7 @@
* Test a refocus operator against the refocus_reference image
* @param impl version of refocus to run
*/
- private void refocus(RenderScriptTask.script impl, double minimumPSNR) {
+ private void refocus(RenderScriptTask.script impl, double minimumPSNR) throws IOException {
Context ctx = getContext();
RenderScript rs = RenderScript.create(ctx);
@@ -63,27 +63,26 @@
current_rgbz = new RGBZ(getResourceRef(R.drawable.test_image),
getResourceRef(R.drawable.test_depthmap),
ctx.getContentResolver(), ctx);
- } catch (IOException e) {
- e.printStackTrace();
- assertNull(e);
+ DepthOfFieldOptions current_depth_options = new DepthOfFieldOptions(current_rgbz);
+ RsTaskParams rsTaskParam = new RsTaskParams(rs, current_depth_options);
+
+ RenderScriptTask renderScriptTask = new RenderScriptTask(rs, impl);
+ Bitmap outputImage = renderScriptTask.applyRefocusFilter(rsTaskParam.mOptions);
+
+ Bitmap expectedImage = BitmapFactory.decodeResource(ctx.getResources(),
+ R.drawable.expected_output);
+
+ double psnr = ImageCompare.psnr(outputImage, expectedImage);
+ android.util.Log.i("RefocusTest", "psnr = " + String.format("%.02f", psnr));
+ if (psnr < minimumPSNR) {
+ MediaStoreSaver.savePNG(outputImage, "refocus", "refocus_output" , ctx);
+ assertTrue("Required minimum psnr = " + String.format("%.02f; ", minimumPSNR) +
+ "Actual psnr = " + String.format("%.02f", psnr),
+ false);
+ }
+ } finally {
+ rs.destroy();
}
- DepthOfFieldOptions current_depth_options = new DepthOfFieldOptions(current_rgbz);
- RsTaskParams rsTaskParam = new RsTaskParams(rs, current_depth_options);
-
- RenderScriptTask renderScriptTask = new RenderScriptTask(rs, impl);
- Bitmap outputImage = renderScriptTask.applyRefocusFilter(rsTaskParam.mOptions);
-
- Bitmap expectedImage = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.expected_output);
-
- double psnr = ImageCompare.psnr(outputImage, expectedImage);
- android.util.Log.i("RefocusTest", "psnr = " + String.format("%.02f", psnr));
- if (psnr < minimumPSNR) {
- MediaStoreSaver.savePNG(outputImage, "refocus", "refocus_output" , ctx);
- assertTrue("Required minimum psnr = " + String.format("%.02f; ", minimumPSNR) +
- "Actual psnr = " + String.format("%.02f", psnr),
- false);
- }
- rs.destroy();
}
diff --git a/tests/tests/renderscriptlegacy/AndroidTest.xml b/tests/tests/renderscriptlegacy/AndroidTest.xml
index 6550d5d..127ace1 100644
--- a/tests/tests/renderscriptlegacy/AndroidTest.xml
+++ b/tests/tests/renderscriptlegacy/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Renderscript legacy Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="renderscript" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/rsblas/Android.mk b/tests/tests/rsblas/Android.mk
index 0385573..637f08e 100644
--- a/tests/tests/rsblas/Android.mk
+++ b/tests/tests/rsblas/Android.mk
@@ -27,7 +27,8 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_JNI_SHARED_LIBRARIES := libbnnmdata_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
diff --git a/tests/tests/rsblas/AndroidTest.xml b/tests/tests/rsblas/AndroidTest.xml
index 5f8164b..7cbfe32 100644
--- a/tests/tests/rsblas/AndroidTest.xml
+++ b/tests/tests/rsblas/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS RS BLAS test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="renderscript" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/rscpp/Android.mk b/tests/tests/rscpp/Android.mk
index 11957bf..f5c20ca 100644
--- a/tests/tests/rscpp/Android.mk
+++ b/tests/tests/rscpp/Android.mk
@@ -28,7 +28,8 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_JNI_SHARED_LIBRARIES := librscpptest_jni
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
diff --git a/tests/tests/rscpp/AndroidTest.xml b/tests/tests/rscpp/AndroidTest.xml
index 9a9c6ba..61d05a8 100644
--- a/tests/tests/rscpp/AndroidTest.xml
+++ b/tests/tests/rscpp/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for renderscript cpp Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="renderscript" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/sax/Android.mk b/tests/tests/sax/Android.mk
index 0c2a2b3..d854016 100644
--- a/tests/tests/sax/Android.mk
+++ b/tests/tests/sax/Android.mk
@@ -21,7 +21,9 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/sax/AndroidTest.xml b/tests/tests/sax/AndroidTest.xml
index 20609bc..f81aa67 100644
--- a/tests/tests/sax/AndroidTest.xml
+++ b/tests/tests/sax/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS SAX test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index b22f6c7..62952ef 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -28,10 +28,13 @@
ctstestrunner \
compatibility-device-util \
guava \
- platform-test-annotations \
- legacy-android-test
+ platform-test-annotations
-LOCAL_JAVA_LIBRARIES := android.test.runner org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ org.apache.http.legacy \
+ android.test.base \
+
LOCAL_JNI_SHARED_LIBRARIES := libctssecurity_jni libcts_jni libnativehelper_compat_libc++ \
libnativehelper \
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 2c47d89..03b2f9a 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS security test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index fe4f779..4fefdab 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -28,7 +28,6 @@
android_security_cts_LinuxRngTest.cpp \
android_security_cts_NativeCodeTest.cpp \
android_security_cts_SELinuxTest.cpp \
- android_security_cts_SeccompTest.cpp \
android_security_cts_MMapExecutableTest.cpp \
android_security_cts_EncryptionTest.cpp \
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index 835d1a6..2014591 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -49,11 +49,7 @@
return JNI_ERR;
}
- if (register_android_security_cts_SeccompTest(env)) {
- return JNI_ERR;
- }
-
- if (register_android_security_cts_KernelSettingsTest(env)) {
+ if (register_android_security_cts_KernelSettingsTest(env)) {
return JNI_ERR;
}
diff --git a/tests/tests/security/jni/android_security_cts_SeccompTest.cpp b/tests/tests/security/jni/android_security_cts_SeccompTest.cpp
deleted file mode 100644
index ee36cdd..0000000
--- a/tests/tests/security/jni/android_security_cts_SeccompTest.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <jni.h>
-
-#define LOG_TAG "SeccompTest"
-
-#include <cutils/log.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-/*
- * Function: testSyscallBlocked
- * Purpose: test that the syscall listed is blocked by seccomp
- * Parameters:
- * nr: syscall number
- * Returns:
- * 1 if blocked, else 0
- * Exceptions: None
- */
-static jboolean testSyscallBlocked(JNIEnv *, jobject, int nr) {
- int pid = fork();
- if (pid == 0) {
- ALOGI("Calling syscall %d", nr);
- int ret = syscall(nr);
- return false;
- } else {
- int status;
- int ret = waitpid(pid, &status, 0);
- if (ret != pid) {
- ALOGE("Unexpected return result from waitpid");
- return false;
- }
-
- if (WIFEXITED(status)) {
- ALOGE("syscall was not blocked");
- return false;
- }
-
- if (WIFSIGNALED(status)) {
- int signal = WTERMSIG(status);
- if (signal == 31) {
- ALOGI("syscall caused process termination");
- return true;
- }
-
- ALOGE("Unexpected signal");
- return false;
- }
-
- ALOGE("Unexpected status from syscall_exists");
- return false;
- }
-}
-
-static JNINativeMethod gMethods[] = {
- { "testSyscallBlocked", "(I)Z",
- (void*) testSyscallBlocked },
-};
-
-int register_android_security_cts_SeccompTest(JNIEnv* env)
-{
- jclass clazz = env->FindClass("android/security/cts/SeccompTest");
-
- return env->RegisterNatives(clazz, gMethods,
- sizeof(gMethods) / sizeof(JNINativeMethod));
-}
diff --git a/tests/tests/security/res/raw/b38116746_new.ico b/tests/tests/security/res/raw/b38116746_new.ico
new file mode 100644
index 0000000..35ee5b5
--- /dev/null
+++ b/tests/tests/security/res/raw/b38116746_new.ico
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_67381469.webp b/tests/tests/security/res/raw/bug_67381469.webp
new file mode 100644
index 0000000..82ff6e4
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_67381469.webp
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java b/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
index c8bfbb1..203db12 100644
--- a/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
+++ b/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
@@ -17,30 +17,56 @@
package android.security.cts;
import android.graphics.BitmapFactory;
+import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.SecurityTest;
import android.test.AndroidTestCase;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.InputStream;
+import java.lang.Exception;
+
import android.security.cts.R;
@SecurityTest
public class BitmapFactorySecurityTests extends AndroidTestCase {
- private InputStream getResource(int resId) {
- InputStream resource = mContext.getResources().openRawResource(R.raw.bug_38116746);
- assertNotNull(resource);
- return resource;
+ private FileDescriptor getResource(int resId) {
+ try {
+ InputStream is = mContext.getResources().openRawResource(resId);
+ assertNotNull(is);
+ File file = File.createTempFile("BitmapFactorySecurityFile" + resId, "img");
+ file.deleteOnExit();
+ FileOutputStream output = new FileOutputStream(file);
+ byte[] buffer = new byte[1024];
+ int readLength;
+ while ((readLength = is.read(buffer)) != -1) {
+ output.write(buffer, 0, readLength);
+ }
+ is.close();
+ output.close();
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ return pfd.getFileDescriptor();
+ } catch (Exception e) {
+ fail("Could not get resource " + resId + "! " + e);
+ return null;
+ }
}
/**
- * Verifies that decoding a corrupt ICO does not run out of memory.
+ * Verifies that decoding a corrupt ICO does crash.
*/
public void test_android_bug_38116746() {
- InputStream exploitImage = getResource(R.raw.bug_38116746);
+ FileDescriptor exploitImage = getResource(R.raw.bug_38116746);
try {
- BitmapFactory.decodeStream(exploitImage);
+ BitmapFactory.decodeFileDescriptor(exploitImage);
} catch (OutOfMemoryError e) {
fail("OOM attempting to decode ICO");
}
+
+ // This previously crashed in fread. No need to check the output.
+ BitmapFactory.decodeFileDescriptor(getResource(R.raw.b38116746_new));
}
}
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index b880f5f..68ec092 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -36,7 +36,6 @@
"75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
"DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13",
"74:20:74:41:72:9C:DD:92:EC:79:31:D8:23:10:8D:C2:81:92:E2:BB",
- "40:54:DA:6F:1C:3F:40:74:AC:ED:0F:EC:CD:DB:79:D1:53:FB:90:1D",
"F4:8B:11:BF:DE:AB:BE:94:54:20:71:E6:41:DE:6B:BE:88:2B:40:B9",
"58:E8:AB:B0:36:15:33:FB:80:F7:9B:1B:6D:29:D3:FF:8D:5F:00:F0",
"55:A6:72:3E:CB:F2:EC:CD:C3:23:74:70:19:9D:2A:BE:11:E3:81:D1",
@@ -51,6 +50,7 @@
"DA:FA:F7:FA:66:84:EC:06:8F:14:50:BD:C7:C2:81:A5:BC:A9:64:57",
"74:F8:A3:C3:EF:E7:B3:90:06:4B:83:90:3C:21:64:60:20:E5:DF:CE",
"31:43:64:9B:EC:CE:27:EC:ED:3A:3F:0B:8F:0D:E4:E8:91:DD:EE:CA",
+ "B7:AB:33:08:D1:EA:44:77:BA:14:80:12:5A:6F:BD:A9:36:49:0C:BB",
"5F:43:E5:B1:BF:F8:78:8C:AC:1C:C7:CA:4A:9A:C6:22:2B:CC:34:C6",
"2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E",
"79:5F:88:60:C5:AB:7C:3D:92:E6:CB:F4:8D:E1:45:CD:11:EF:60:0B",
@@ -64,7 +64,6 @@
"59:AF:82:79:91:86:C7:B4:75:07:CB:CF:03:57:46:EB:04:DD:B7:16",
"50:30:06:09:1D:97:D4:F5:AE:39:F7:CB:E7:92:7D:7D:65:2D:34:31",
"FE:45:65:9B:79:03:5B:98:A1:61:B5:51:2E:AC:DA:58:09:48:22:4D",
- "1B:4B:39:61:26:27:6B:64:91:A2:68:6D:D7:02:43:21:2D:1F:1D:96",
"8C:F4:27:FD:79:0C:3A:D1:66:06:8D:E8:1E:57:EF:BB:93:22:72:D4",
"2F:78:3D:25:52:18:A7:4A:65:39:71:B5:2C:A2:9C:45:15:6F:E9:19",
"BA:29:41:60:77:98:3F:F4:F3:EF:F2:31:05:3B:2E:EA:6D:4D:45:FD",
@@ -73,6 +72,7 @@
"36:79:CA:35:66:87:72:30:4D:30:A5:FB:87:3B:0F:A7:7B:B7:0D:54",
"1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
"6E:26:64:F3:56:BF:34:55:BF:D1:93:3F:7C:01:DE:D8:13:DA:8A:A6",
+ "74:3A:F0:52:9B:D0:32:A0:F4:4A:83:CD:D4:BA:A9:7B:7C:2E:C4:9A",
"D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
"66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
"DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
@@ -83,14 +83,14 @@
"43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
"F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
"05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
- "62:52:DC:40:F7:11:43:A2:2F:DE:9E:F7:34:8E:06:42:51:B1:81:18",
"70:17:9B:86:8C:00:A4:FA:60:91:52:22:3F:9F:3E:32:BD:E0:05:62",
"D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
"B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
- "2E:14:DA:EC:28:F0:FA:1E:8E:38:9A:4E:AB:EB:26:C0:0A:D3:83:C3",
+ "4C:DD:51:A3:D1:F5:20:32:14:B0:C6:C5:32:23:03:91:C7:46:42:6D",
"DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12",
"0D:44:DD:8C:3C:8C:1A:1A:58:75:64:81:E9:0F:2E:2A:FF:B3:D2:6E",
"CA:3A:FB:CF:12:40:36:4B:44:B2:16:20:88:80:48:39:19:93:7C:F7",
+ "FF:BD:CD:E7:82:C8:43:5E:3C:6F:26:86:5C:CA:A8:3A:45:5B:C3:0A",
"13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
"5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
"49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
@@ -98,6 +98,7 @@
"29:36:21:02:8B:20:ED:02:F5:66:C5:32:D1:D6:ED:90:9F:45:00:2F",
"37:9A:19:7B:41:85:45:35:0C:A6:03:69:F3:3C:2E:AF:47:4F:20:79",
"FA:B7:EE:36:97:26:62:FB:2D:B0:2A:F6:BF:03:FD:E8:7C:4B:2F:9B",
+ "C3:19:7C:39:24:E6:54:AF:1B:C4:AB:20:95:7A:E2:C3:0E:13:02:6A",
"9F:74:4E:9F:2B:4D:BA:EC:0F:31:2C:50:B6:56:3B:8E:2D:93:C3:11",
"A1:4B:48:D9:43:EE:0A:0E:40:90:4F:3C:E0:A4:C0:91:93:51:5D:3F",
"C9:A8:B9:E7:55:80:5E:58:E3:53:77:A7:25:EB:AF:C3:7B:27:CC:D7",
@@ -119,6 +120,7 @@
"AA:DB:BC:22:23:8F:C4:01:A1:27:BB:38:DD:F4:1D:DB:08:9E:F0:12",
"E2:52:FA:95:3F:ED:DB:24:60:BD:6E:28:F3:9C:CC:CF:5E:B3:3F:DE",
"3B:C4:9F:48:F8:F3:73:A0:9C:1E:BD:F8:5B:B1:C3:65:C7:D8:11:B3",
+ "0F:36:38:5B:81:1A:25:C3:9B:31:4E:83:CA:E9:34:66:70:CC:74:B4",
"28:90:3A:63:5B:52:80:FA:E6:77:4C:0B:6D:A7:D6:BA:A6:4A:F2:E8",
"9C:BB:48:53:F6:A4:F6:D3:52:A4:E8:32:52:55:60:13:F5:AD:AF:65",
"B1:BC:96:8B:D4:F4:9D:62:2A:A8:9A:81:F2:15:01:52:A4:1D:82:9C",
@@ -129,6 +131,7 @@
"47:BE:AB:C9:22:EA:E8:0E:78:78:34:62:A7:9F:45:C2:54:FD:E6:8B",
"3A:44:73:5A:E5:81:90:1F:24:86:61:46:1E:3B:9C:C4:5F:F5:3A:1B",
"B3:1E:B1:B7:40:E3:6C:84:02:DA:DC:37:D4:4D:F5:D4:67:49:52:F9",
+ "58:D1:DF:95:95:67:6B:63:C0:F0:5B:1C:17:4D:8B:84:0B:C8:78:BD",
"F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
"3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
"03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
@@ -139,12 +142,12 @@
"B8:23:6B:00:2F:1D:16:86:53:01:55:6C:11:A4:37:CA:EB:FF:C3:BB",
"87:82:C6:C3:04:35:3B:CF:D2:96:92:D2:59:3E:7D:44:D9:34:FF:11",
"59:0D:2D:7D:88:4F:40:2E:61:7E:A5:62:32:17:65:CF:17:D8:94:E9",
+ "B8:BE:6D:CB:56:F1:55:B9:63:D4:12:CA:4E:06:34:C7:94:B2:1C:C0",
"AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
"DF:71:7E:AA:4A:D9:4E:C9:55:84:99:60:2D:48:DE:5F:BC:F0:3A:25",
"F6:10:84:07:D6:F8:BB:67:98:0C:C2:E2:44:C2:EB:AE:1C:EF:63:BE",
"AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4",
"5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
- "F1:7F:6F:B6:31:DC:99:E3:A3:C8:7F:FE:1C:F1:81:10:88:D9:60:33",
"9D:70:BB:01:A5:A4:A0:18:11:2E:F7:1C:01:B9:32:C5:34:E7:88:A8",
"96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
"4F:65:8E:1F:E9:06:D8:28:02:E9:54:47:41:C9:54:25:5D:69:CC:1A",
@@ -155,7 +158,6 @@
"F9:B5:B6:32:45:5F:9C:BE:EC:57:5F:80:DC:E9:6E:2C:C7:B2:78:B7",
"E6:21:F3:35:43:79:05:9A:4B:68:30:9D:8A:2F:74:22:15:87:EC:79",
"89:DF:74:FE:5C:F4:0F:4A:80:F9:E3:37:7D:54:DA:91:E1:01:31:8E",
- "E0:B4:32:2E:B2:F6:A5:68:B6:54:53:84:48:18:4A:50:36:87:43:84",
"7E:04:DE:89:6A:3E:66:6D:00:E6:87:D3:3F:FA:D9:3B:E8:3D:34:9E",
"6E:3A:55:A4:19:0C:19:5C:93:84:3C:C0:DB:72:2E:31:30:61:F0:B1",
"4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5",
diff --git a/tests/tests/security/src/android/security/cts/DecodeTest.java b/tests/tests/security/src/android/security/cts/DecodeTest.java
index e64e37a..0e92310 100644
--- a/tests/tests/security/src/android/security/cts/DecodeTest.java
+++ b/tests/tests/security/src/android/security/cts/DecodeTest.java
@@ -38,4 +38,17 @@
Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
assertNull(bitmap);
}
+
+ /**
+ * Verifies that the device fails to decode a truncated animated webp.
+ *
+ * Prior to fixing bug 67381469, decoding this file would crash. Instead, it should fail to
+ * decode.
+ */
+ @SecurityTest
+ public void test_android_bug_67381469() {
+ InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_67381469);
+ Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
+ assertNull(bitmap);
+ }
}
diff --git a/tests/tests/security/src/android/security/cts/OpenSSLEarlyCCSTest.java b/tests/tests/security/src/android/security/cts/OpenSSLEarlyCCSTest.java
deleted file mode 100644
index a6bad84..0000000
--- a/tests/tests/security/src/android/security/cts/OpenSSLEarlyCCSTest.java
+++ /dev/null
@@ -1,582 +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 android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import android.security.cts.OpenSSLHeartbleedTest.AlertMessage;
-import android.security.cts.OpenSSLHeartbleedTest.HandshakeMessage;
-import android.security.cts.OpenSSLHeartbleedTest.HardcodedCertX509KeyManager;
-import android.security.cts.OpenSSLHeartbleedTest.TlsProtocols;
-import android.security.cts.OpenSSLHeartbleedTest.TlsRecord;
-import android.security.cts.OpenSSLHeartbleedTest.TlsRecordReader;
-import android.security.cts.OpenSSLHeartbleedTest.TrustAllX509TrustManager;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.security.KeyFactory;
-import java.security.PrivateKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ServerSocketFactory;
-import javax.net.SocketFactory;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-
-/**
- * Tests for the OpenSSL early ChangeCipherSpec (CCS) vulnerability (CVE-2014-0224).
- */
-@SecurityTest
-public class OpenSSLEarlyCCSTest extends InstrumentationTestCase {
-
- // IMPLEMENTATION NOTE: This test spawns an SSLSocket client, SSLServerSocket server, and a
- // Man-in-The-Middle (MiTM). The client connects to the MiTM which then connects to the server
- // and starts forwarding all TLS records between the client and the server. In tests that check
- // for the early CCS vulnerability, the MiTM also injects an early ChangeCipherSpec record into
- // the traffic to which a correctly implemented peer is supposed to reply by immediately
- // aborting the TLS handshake with an unexpected_message fatal alert.
-
- // IMPLEMENTATION NOTE: This test spawns several background threads that perform network I/O
- // on localhost. To ensure that these background threads are cleaned up at the end of the test,
- // tearDown() kills the sockets they may be using. To aid this behavior, all Socket and
- // ServerSocket instances are available as fields of this class. These fields should be accessed
- // via setters and getters to avoid memory visibility issues due to concurrency.
-
- private static final String TAG = OpenSSLEarlyCCSTest.class.getSimpleName();
-
- private SSLServerSocket mServerListeningSocket;
- private SSLSocket mServerSocket;
- private SSLSocket mClientSocket;
- private ServerSocket mMitmListeningSocket;
- private Socket mMitmServerSocket;
- private Socket mMitmClientSocket;
- private ExecutorService mExecutorService;
-
- private boolean mCCSWasInjected;
- private TlsRecord mFirstRecordReceivedAfterCCSWasInjected;
- private boolean mFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted;
-
- @Override
- protected void tearDown() throws Exception {
- Log.i(TAG, "Tearing down");
- try {
- if (mExecutorService != null) {
- mExecutorService.shutdownNow();
- }
- OpenSSLHeartbleedTest.closeQuietly(getServerListeningSocket());
- OpenSSLHeartbleedTest.closeQuietly(getServerSocket());
- OpenSSLHeartbleedTest.closeQuietly(getClientSocket());
- OpenSSLHeartbleedTest.closeQuietly(getMitmListeningSocket());
- OpenSSLHeartbleedTest.closeQuietly(getMitmServerSocket());
- OpenSSLHeartbleedTest.closeQuietly(getMitmClientSocket());
- } finally {
- super.tearDown();
- Log.i(TAG, "Tear down completed");
- }
- }
-
- /**
- * Tests that TLS handshake succeeds when the MiTM simply forwards all data without tampering
- * with it. This is to catch issues unrelated to early CCS.
- */
- public void testWithoutEarlyCCS() throws Exception {
- handshake(false, false);
- }
-
- /**
- * Tests whether client sockets are vulnerable to early CCS.
- */
- public void testEarlyCCSInjectedIntoClient() throws Exception {
- checkEarlyCCS(true);
- }
-
- /**
- * Tests whether server sockets are vulnerable to early CCS.
- */
- public void testEarlyCCSInjectedIntoServer() throws Exception {
- checkEarlyCCS(false);
- }
-
- /**
- * Tests for the early CCS vulnerability.
- *
- * @param client {@code true} to test the client, {@code false} to test the server.
- */
- private void checkEarlyCCS(boolean client) throws Exception {
- // IMPLEMENTATION NOTE: The MiTM is forwarding all TLS records between the client and the
- // server unmodified. Additionally, the MiTM transmits an early ChangeCipherSpec to server
- // (if "client" argument is false) right before client's ClientKeyExchange or to client (if
- // "client" argument is true) right before server's Certificate. The peer is expected to
- // to abort the handshake immediately with unexpected_message alert.
- try {
- handshake(true, client);
- // TLS handshake succeeded
- assertTrue("Early CCS injected", wasCCSInjected());
- fail("Handshake succeeded despite early CCS having been injected");
- } catch (ExecutionException e) {
- // TLS handshake failed
- assertTrue("Early CCS injected", wasCCSInjected());
- TlsRecord firstRecordReceivedAfterCCSWasInjected =
- getFirstRecordReceivedAfterCCSWasInjected();
- assertNotNull(
- "Nothing received after early CCS was injected",
- firstRecordReceivedAfterCCSWasInjected);
- if (firstRecordReceivedAfterCCSWasInjected.protocol == TlsProtocols.ALERT) {
- AlertMessage alert = AlertMessage.tryParse(firstRecordReceivedAfterCCSWasInjected);
- if ((alert != null)
- && (alert.level == AlertMessage.LEVEL_FATAL)
- && (alert.description == AlertMessage.DESCRIPTION_UNEXPECTED_MESSAGE)) {
- // Expected/correct response to an early CCS
- return;
- }
- }
- fail("SSLSocket is vulnerable to early CCS in " + ((client) ? "client" : "server")
- + " mode: unexpected record received after CCS was injected: "
- + getRecordInfo(
- getFirstRecordReceivedAfterCCSWasInjected(),
- getFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted()));
- }
- }
-
- /**
- * Starts the client, server, and the MiTM. Makes the client and server perform a TLS handshake
- * and exchange application-level data. The MiTM injects a ChangeCipherSpec record if requested
- * by {@code earlyCCSInjected}. The direction of the injected message is specified by
- * {@code injectedIntoClient}.
- */
- private void handshake(
- final boolean earlyCCSInjected,
- final boolean injectedIntoClient) throws Exception {
- mExecutorService = Executors.newFixedThreadPool(4);
- setServerListeningSocket(serverBind());
- final SocketAddress serverAddress = getServerListeningSocket().getLocalSocketAddress();
- Log.i(TAG, "Server bound to " + serverAddress);
-
- setMitmListeningSocket(mitmBind());
- final SocketAddress mitmAddress = getMitmListeningSocket().getLocalSocketAddress();
- Log.i(TAG, "MiTM bound to " + mitmAddress);
-
- // Start the MiTM daemon in the background
- mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- mitmAcceptAndForward(serverAddress, earlyCCSInjected, injectedIntoClient);
- return null;
- }
- });
- // Start the server in the background
- Future<Void> serverFuture = mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- serverAcceptAndHandshake();
- return null;
- }
- });
- // Start the client in the background
- Future<Void> clientFuture = mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- clientConnectAndHandshake(mitmAddress);
- return null;
- }
- });
-
- // Wait for both client and server to terminate, to ensure that we observe all the traffic
- // exchanged between them. Throw an exception if one of them failed.
- Log.i(TAG, "Waiting for client");
- // Wait for the client, but don't yet throw an exception if it failed.
- Exception clientException = null;
- try {
- clientFuture.get(10, TimeUnit.SECONDS);
- } catch (Exception e) {
- clientException = e;
- }
- Log.i(TAG, "Waiting for server");
- // Wait for the server and throw an exception if it failed.
- serverFuture.get(5, TimeUnit.SECONDS);
- // Throw an exception if the client failed.
- if (clientException != null) {
- throw clientException;
- }
- Log.i(TAG, "Handshake completed and application data exchanged");
- }
-
- private void clientConnectAndHandshake(SocketAddress serverAddress) throws Exception {
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(
- null,
- new TrustManager[] {new TrustAllX509TrustManager()},
- null);
- SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket();
- setClientSocket(socket);
- try {
- Log.i(TAG, "Client connecting to " + serverAddress);
- socket.connect(serverAddress);
- Log.i(TAG, "Client connected to server from " + socket.getLocalSocketAddress());
- // Ensure a TLS handshake is performed and an exception is thrown if it fails.
- socket.getOutputStream().write("client".getBytes());
- socket.getOutputStream().flush();
- Log.i(TAG, "Client sent request. Reading response");
- int b = socket.getInputStream().read();
- Log.i(TAG, "Client read response: " + b);
- } catch (Exception e) {
- // Make sure the test log includes exceptions from all parties involved.
- Log.w(TAG, "Client failed", e);
- throw e;
- } finally {
- socket.close();
- }
- }
-
- private SSLServerSocket serverBind() throws Exception {
- // Load the server's private key and cert chain
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(
- OpenSSLHeartbleedTest.readResource(
- getInstrumentation().getContext(), R.raw.openssl_heartbleed_test_key)));
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- X509Certificate[] certChain = new X509Certificate[] {
- (X509Certificate) certFactory.generateCertificate(
- new ByteArrayInputStream(OpenSSLHeartbleedTest.readResource(
- getInstrumentation().getContext(),
- R.raw.openssl_heartbleed_test_cert)))
- };
-
- // Initialize TLS context to use the private key and cert chain for server sockets
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(
- new KeyManager[] {new HardcodedCertX509KeyManager(privateKey, certChain)},
- null,
- null);
-
- Log.i(TAG, "Server binding to local port");
- return (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(0);
- }
-
- private void serverAcceptAndHandshake() throws Exception {
- SSLSocket socket = null;
- SSLServerSocket serverSocket = getServerListeningSocket();
- try {
- Log.i(TAG, "Server listening for incoming connection");
- socket = (SSLSocket) serverSocket.accept();
- setServerSocket(socket);
- Log.i(TAG, "Server accepted connection from " + socket.getRemoteSocketAddress());
- // Ensure a TLS handshake is performed and an exception is thrown if it fails.
- socket.getOutputStream().write("server".getBytes());
- socket.getOutputStream().flush();
- Log.i(TAG, "Server sent reply. Reading response");
- int b = socket.getInputStream().read();
- Log.i(TAG, "Server read response: " + b);
- } catch (Exception e) {
- // Make sure the test log includes exceptions from all parties involved.
- Log.w(TAG, "Server failed", e);
- throw e;
- } finally {
- if (socket != null) {
- socket.close();
- }
- }
- }
-
- private ServerSocket mitmBind() throws Exception {
- Log.i(TAG, "MiTM binding to local port");
- return ServerSocketFactory.getDefault().createServerSocket(0);
- }
-
- /**
- * Accepts the connection on the MiTM listening socket, forwards the TLS records between the
- * client and the server, and, if requested, injects an early {@code ChangeCipherSpec} record.
- *
- * @param injectEarlyCCS whether to inject an early {@code ChangeCipherSpec} record.
- * @param injectIntoClient when {@code injectEarlyCCS} is {@code true}, whether to inject the
- * {@code ChangeCipherSpec} record into client or into server.
- */
- private void mitmAcceptAndForward(
- SocketAddress serverAddress,
- final boolean injectEarlyCCS,
- final boolean injectIntoClient) throws Exception {
- Socket clientSocket = null;
- Socket serverSocket = null;
- ServerSocket listeningSocket = getMitmListeningSocket();
- try {
- Log.i(TAG, "MiTM waiting for incoming connection");
- clientSocket = listeningSocket.accept();
- setMitmClientSocket(clientSocket);
- Log.i(TAG, "MiTM accepted connection from " + clientSocket.getRemoteSocketAddress());
- serverSocket = SocketFactory.getDefault().createSocket();
- setMitmServerSocket(serverSocket);
- Log.i(TAG, "MiTM connecting to server " + serverAddress);
- serverSocket.connect(serverAddress, 10000);
- Log.i(TAG, "MiTM connected to server from " + serverSocket.getLocalSocketAddress());
- final InputStream serverInputStream = serverSocket.getInputStream();
- final OutputStream clientOutputStream = clientSocket.getOutputStream();
- Future<Void> serverToClientTask = mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- // Inject early ChangeCipherSpec before Certificate, if requested
- forwardTlsRecords(
- "MiTM S->C",
- serverInputStream,
- clientOutputStream,
- (injectEarlyCCS && injectIntoClient)
- ? HandshakeMessage.TYPE_CERTIFICATE : -1);
- return null;
- }
- });
- // Inject early ChangeCipherSpec before ClientKeyExchange, if requested
- forwardTlsRecords(
- "MiTM C->S",
- clientSocket.getInputStream(),
- serverSocket.getOutputStream(),
- (injectEarlyCCS && !injectIntoClient)
- ? HandshakeMessage.TYPE_CLIENT_KEY_EXCHANGE : -1);
- serverToClientTask.get(10, TimeUnit.SECONDS);
- } catch (Exception e) {
- // Make sure the test log includes exceptions from all parties involved.
- Log.w(TAG, "MiTM failed", e);
- throw e;
- } finally {
- OpenSSLHeartbleedTest.closeQuietly(clientSocket);
- OpenSSLHeartbleedTest.closeQuietly(serverSocket);
- }
- }
-
- /**
- * Forwards TLS records from the provided {@code InputStream} to the provided
- * {@code OutputStream}. If requested, injects an early {@code ChangeCipherSpec}.
- */
- private void forwardTlsRecords(
- String logPrefix,
- InputStream in,
- OutputStream out,
- int handshakeMessageTypeBeforeWhichToInjectEarlyCCS) throws Exception {
- Log.i(TAG, logPrefix + ": record forwarding started");
- boolean interestingRecordsLogged =
- handshakeMessageTypeBeforeWhichToInjectEarlyCCS == -1;
- try {
- TlsRecordReader reader = new TlsRecordReader(in);
- byte[] recordBytes;
- // Fragments contained in records may be encrypted after a certain point in the
- // handshake. Once they are encrypted, this MiTM cannot inspect their plaintext which.
- boolean fragmentEncryptionMayBeEnabled = false;
- while ((recordBytes = reader.readRecord()) != null) {
- TlsRecord record = TlsRecord.parse(recordBytes);
- forwardTlsRecord(logPrefix,
- recordBytes,
- record,
- fragmentEncryptionMayBeEnabled,
- out,
- interestingRecordsLogged,
- handshakeMessageTypeBeforeWhichToInjectEarlyCCS);
- if (record.protocol == TlsProtocols.CHANGE_CIPHER_SPEC) {
- fragmentEncryptionMayBeEnabled = true;
- }
- }
- } finally {
- Log.d(TAG, logPrefix + ": record forwarding finished");
- }
- }
-
- private void forwardTlsRecord(
- String logPrefix,
- byte[] recordBytes,
- TlsRecord record,
- boolean fragmentEncryptionMayBeEnabled,
- OutputStream out,
- boolean interestingRecordsLogged,
- int handshakeMessageTypeBeforeWhichToInjectCCS) throws IOException {
- // Save information about the record if it's of interest to this test
- if (interestingRecordsLogged) {
- if (wasCCSInjected()) {
- setFirstRecordReceivedAfterCCSWasInjected(record, fragmentEncryptionMayBeEnabled);
- }
- } else if ((record.protocol == TlsProtocols.CHANGE_CIPHER_SPEC)
- && (handshakeMessageTypeBeforeWhichToInjectCCS != -1)) {
- // Do not forward original ChangeCipherSpec record(s) if we're injecting such a record
- // ourselves. This is to make sure that the peer sees only one ChangeCipherSpec.
- Log.i(TAG, logPrefix + ": Dropping TLS record. "
- + getRecordInfo(record, fragmentEncryptionMayBeEnabled));
- return;
- }
-
- // Inject a ChangeCipherSpec, if necessary, before the specified handshake message type
- if (handshakeMessageTypeBeforeWhichToInjectCCS != -1) {
- if ((!fragmentEncryptionMayBeEnabled) && (OpenSSLHeartbleedTest.isHandshakeMessageType(
- record, handshakeMessageTypeBeforeWhichToInjectCCS))) {
- Log.i(TAG, logPrefix + ": Injecting ChangeCipherSpec");
- setCCSWasInjected();
- out.write(createChangeCipherSpecRecord(record.versionMajor, record.versionMinor));
- out.flush();
- }
- }
-
- Log.i(TAG, logPrefix + ": Forwarding TLS record. "
- + getRecordInfo(record, fragmentEncryptionMayBeEnabled));
- out.write(recordBytes);
- out.flush();
- }
-
- private static String getRecordInfo(TlsRecord record, boolean mayBeEncrypted) {
- StringBuilder result = new StringBuilder();
- result.append(getProtocolName(record.protocol))
- .append(", ")
- .append(getFragmentInfo(record, mayBeEncrypted));
- return result.toString();
- }
-
- private static String getProtocolName(int protocol) {
- switch (protocol) {
- case TlsProtocols.ALERT:
- return "alert";
- case TlsProtocols.APPLICATION_DATA:
- return "application data";
- case TlsProtocols.CHANGE_CIPHER_SPEC:
- return "change cipher spec";
- case TlsProtocols.HANDSHAKE:
- return "handshake";
- default:
- return String.valueOf(protocol);
- }
- }
-
- private static String getFragmentInfo(TlsRecord record, boolean mayBeEncrypted) {
- StringBuilder result = new StringBuilder();
- if (mayBeEncrypted) {
- result.append("encrypted?");
- } else {
- switch (record.protocol) {
- case TlsProtocols.ALERT:
- result.append("level: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a")
- + ", description: "
- + ((record.fragment.length > 1)
- ? String.valueOf(record.fragment[1] & 0xff) : "n/a"));
- break;
- case TlsProtocols.APPLICATION_DATA:
- break;
- case TlsProtocols.CHANGE_CIPHER_SPEC:
- result.append("payload: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
- break;
- case TlsProtocols.HANDSHAKE:
- result.append("type: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
- break;
- }
- }
- result.append(", ").append("fragment length: " + record.fragment.length);
- return result.toString();
- }
-
- private synchronized void setServerListeningSocket(SSLServerSocket socket) {
- mServerListeningSocket = socket;
- }
-
- private synchronized SSLServerSocket getServerListeningSocket() {
- return mServerListeningSocket;
- }
-
- private synchronized void setServerSocket(SSLSocket socket) {
- mServerSocket = socket;
- }
-
- private synchronized SSLSocket getServerSocket() {
- return mServerSocket;
- }
-
- private synchronized void setClientSocket(SSLSocket socket) {
- mClientSocket = socket;
- }
-
- private synchronized SSLSocket getClientSocket() {
- return mClientSocket;
- }
-
- private synchronized void setMitmListeningSocket(ServerSocket socket) {
- mMitmListeningSocket = socket;
- }
-
- private synchronized ServerSocket getMitmListeningSocket() {
- return mMitmListeningSocket;
- }
-
- private synchronized void setMitmServerSocket(Socket socket) {
- mMitmServerSocket = socket;
- }
-
- private synchronized Socket getMitmServerSocket() {
- return mMitmServerSocket;
- }
-
- private synchronized void setMitmClientSocket(Socket socket) {
- mMitmClientSocket = socket;
- }
-
- private synchronized Socket getMitmClientSocket() {
- return mMitmClientSocket;
- }
-
- private synchronized void setCCSWasInjected() {
- mCCSWasInjected = true;
- }
-
- private synchronized boolean wasCCSInjected() {
- return mCCSWasInjected;
- }
-
- private synchronized void setFirstRecordReceivedAfterCCSWasInjected(
- TlsRecord record, boolean mayBeEncrypted) {
- if (mFirstRecordReceivedAfterCCSWasInjected == null) {
- mFirstRecordReceivedAfterCCSWasInjected = record;
- mFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted = mayBeEncrypted;
- }
- }
-
- private synchronized TlsRecord getFirstRecordReceivedAfterCCSWasInjected() {
- return mFirstRecordReceivedAfterCCSWasInjected;
- }
-
- private synchronized boolean getFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted() {
- return mFirstRecordReceivedAfterCCSWasInjectedMayBeEncrypted;
- }
-
- private static byte[] createChangeCipherSpecRecord(int versionMajor, int versionMinor) {
- TlsRecord record = new TlsRecord();
- record.protocol = TlsProtocols.CHANGE_CIPHER_SPEC;
- record.versionMajor = versionMajor;
- record.versionMinor = versionMinor;
- record.fragment = new byte[] {1};
- return TlsRecord.unparse(record);
- }
-}
diff --git a/tests/tests/security/src/android/security/cts/OpenSSLHeartbleedTest.java b/tests/tests/security/src/android/security/cts/OpenSSLHeartbleedTest.java
deleted file mode 100644
index 95a82a8..0000000
--- a/tests/tests/security/src/android/security/cts/OpenSSLHeartbleedTest.java
+++ /dev/null
@@ -1,983 +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 android.security.cts;
-
-import android.content.Context;
-import android.platform.test.annotations.SecurityTest;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.security.KeyFactory;
-import java.security.Principal;
-import java.security.PrivateKey;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ServerSocketFactory;
-import javax.net.SocketFactory;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-
-/**
- * Tests for the OpenSSL Heartbleed vulnerability.
- */
-@SecurityTest
-public class OpenSSLHeartbleedTest extends InstrumentationTestCase {
-
- // IMPLEMENTATION NOTE: This test spawns an SSLSocket client, SSLServerSocket server, and a
- // Man-in-The-Middle (MiTM). The client connects to the MiTM which then connects to the server
- // and starts forwarding all TLS records between the client and the server. In tests that check
- // for the Heartbleed vulnerability, the MiTM also injects a HeartbeatRequest message into the
- // traffic.
-
- // IMPLEMENTATION NOTE: This test spawns several background threads that perform network I/O
- // on localhost. To ensure that these background threads are cleaned up at the end of the test
- // tearDown() kills the sockets they may be using. To aid this behavior, all Socket and
- // ServerSocket instances are available as fields of this class. These fields should be accessed
- // via setters and getters to avoid memory visibility issues due to concurrency.
-
- private static final String TAG = OpenSSLHeartbleedTest.class.getSimpleName();
-
- private SSLServerSocket mServerListeningSocket;
- private SSLSocket mServerSocket;
- private SSLSocket mClientSocket;
- private ServerSocket mMitmListeningSocket;
- private Socket mMitmServerSocket;
- private Socket mMitmClientSocket;
- private ExecutorService mExecutorService;
-
- private boolean mHeartbeatRequestWasInjected;
- private boolean mHeartbeatResponseWasDetetected;
- private int mFirstDetectedFatalAlertDescription = -1;
-
- @Override
- protected void tearDown() throws Exception {
- Log.i(TAG, "Tearing down");
- if (mExecutorService != null) {
- mExecutorService.shutdownNow();
- }
- closeQuietly(getServerListeningSocket());
- closeQuietly(getServerSocket());
- closeQuietly(getClientSocket());
- closeQuietly(getMitmListeningSocket());
- closeQuietly(getMitmServerSocket());
- closeQuietly(getMitmClientSocket());
- super.tearDown();
- Log.i(TAG, "Tear down completed");
- }
-
- /**
- * Tests that TLS handshake succeeds when the MiTM simply forwards all data without tampering
- * with it. This is to catch issues unrelated to TLS heartbeats.
- */
- public void testWithoutHeartbeats() throws Exception {
- handshake(false, false);
- }
-
- /**
- * Tests whether client sockets are vulnerable to Heartbleed.
- */
- public void testClientHeartbleed() throws Exception {
- checkHeartbleed(true);
- }
-
- /**
- * Tests whether server sockets are vulnerable to Heartbleed.
- */
- public void testServerHeartbleed() throws Exception {
- checkHeartbleed(false);
- }
-
- /**
- * Tests for Heartbleed.
- *
- * @param client {@code true} to test the client, {@code false} to test the server.
- */
- private void checkHeartbleed(boolean client) throws Exception {
- // IMPLEMENTATION NOTE: The MiTM is forwarding all TLS records between the client and the
- // server unmodified. Additionally, the MiTM transmits a malformed HeartbeatRequest to
- // server (if "client" argument is false) right after client's ClientKeyExchange or to
- // client (if "client" argument is true) right after server's ServerHello. The peer is
- // expected to either ignore the HeartbeatRequest (if heartbeats are supported) or to abort
- // the handshake with unexpected_message alert (if heartbeats are not supported).
- try {
- handshake(true, client);
- } catch (ExecutionException e) {
- assertFalse(
- "SSLSocket is vulnerable to Heartbleed in " + ((client) ? "client" : "server")
- + " mode",
- wasHeartbeatResponseDetected());
- if (e.getCause() instanceof SSLException) {
- // TLS handshake or data exchange failed. Check whether the error was caused by
- // fatal alert unexpected_message
- int alertDescription = getFirstDetectedFatalAlertDescription();
- if (alertDescription == -1) {
- fail("Handshake failed without a fatal alert");
- }
- assertEquals(
- "First fatal alert description received from server",
- AlertMessage.DESCRIPTION_UNEXPECTED_MESSAGE,
- alertDescription);
- return;
- } else {
- throw e;
- }
- }
-
- // TLS handshake succeeded
- assertFalse(
- "SSLSocket is vulnerable to Heartbleed in " + ((client) ? "client" : "server")
- + " mode",
- wasHeartbeatResponseDetected());
- assertTrue("HeartbeatRequest not injected", wasHeartbeatRequestInjected());
- }
-
- /**
- * Starts the client, server, and the MiTM. Makes the client and server perform a TLS handshake
- * and exchange application-level data. The MiTM injects a HeartbeatRequest message if requested
- * by {@code heartbeatRequestInjected}. The direction of the injected message is specified by
- * {@code injectedIntoClient}.
- */
- private void handshake(
- final boolean heartbeatRequestInjected,
- final boolean injectedIntoClient) throws Exception {
- mExecutorService = Executors.newFixedThreadPool(4);
- setServerListeningSocket(serverBind());
- final SocketAddress serverAddress = getServerListeningSocket().getLocalSocketAddress();
- Log.i(TAG, "Server bound to " + serverAddress);
-
- setMitmListeningSocket(mitmBind());
- final SocketAddress mitmAddress = getMitmListeningSocket().getLocalSocketAddress();
- Log.i(TAG, "MiTM bound to " + mitmAddress);
-
- // Start the MiTM daemon in the background
- mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- mitmAcceptAndForward(
- serverAddress,
- heartbeatRequestInjected,
- injectedIntoClient);
- return null;
- }
- });
- // Start the server in the background
- Future<Void> serverFuture = mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- serverAcceptAndHandshake();
- return null;
- }
- });
- // Start the client in the background
- Future<Void> clientFuture = mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- clientConnectAndHandshake(mitmAddress);
- return null;
- }
- });
-
- // Wait for both client and server to terminate, to ensure that we observe all the traffic
- // exchanged between them. Throw an exception if one of them failed.
- Log.i(TAG, "Waiting for client");
- // Wait for the client, but don't yet throw an exception if it failed.
- Exception clientException = null;
- try {
- clientFuture.get(10, TimeUnit.SECONDS);
- } catch (Exception e) {
- clientException = e;
- }
- Log.i(TAG, "Waiting for server");
- // Wait for the server and throw an exception if it failed.
- serverFuture.get(5, TimeUnit.SECONDS);
- // Throw an exception if the client failed.
- if (clientException != null) {
- throw clientException;
- }
- Log.i(TAG, "Handshake completed and application data exchanged");
- }
-
- private void clientConnectAndHandshake(SocketAddress serverAddress) throws Exception {
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(
- null,
- new TrustManager[] {new TrustAllX509TrustManager()},
- null);
- SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket();
- setClientSocket(socket);
- try {
- Log.i(TAG, "Client connecting to " + serverAddress);
- socket.connect(serverAddress);
- Log.i(TAG, "Client connected to server from " + socket.getLocalSocketAddress());
- // Ensure a TLS handshake is performed and an exception is thrown if it fails.
- socket.getOutputStream().write("client".getBytes());
- socket.getOutputStream().flush();
- Log.i(TAG, "Client sent request. Reading response");
- int b = socket.getInputStream().read();
- Log.i(TAG, "Client read response: " + b);
- } catch (Exception e) {
- Log.w(TAG, "Client failed", e);
- throw e;
- } finally {
- socket.close();
- }
- }
-
- public SSLServerSocket serverBind() throws Exception {
- // Load the server's private key and cert chain
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(
- readResource(
- getInstrumentation().getContext(), R.raw.openssl_heartbleed_test_key)));
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- X509Certificate[] certChain = new X509Certificate[] {
- (X509Certificate) certFactory.generateCertificate(
- new ByteArrayInputStream(readResource(
- getInstrumentation().getContext(),
- R.raw.openssl_heartbleed_test_cert)))
- };
-
- // Initialize TLS context to use the private key and cert chain for server sockets
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(
- new KeyManager[] {new HardcodedCertX509KeyManager(privateKey, certChain)},
- null,
- null);
-
- Log.i(TAG, "Server binding to local port");
- return (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(0);
- }
-
- private void serverAcceptAndHandshake() throws Exception {
- SSLSocket socket = null;
- SSLServerSocket serverSocket = getServerListeningSocket();
- try {
- Log.i(TAG, "Server listening for incoming connection");
- socket = (SSLSocket) serverSocket.accept();
- setServerSocket(socket);
- Log.i(TAG, "Server accepted connection from " + socket.getRemoteSocketAddress());
- // Ensure a TLS handshake is performed and an exception is thrown if it fails.
- socket.getOutputStream().write("server".getBytes());
- socket.getOutputStream().flush();
- Log.i(TAG, "Server sent reply. Reading response");
- int b = socket.getInputStream().read();
- Log.i(TAG, "Server read response: " + b);
- } catch (Exception e) {
- Log.w(TAG, "Server failed", e);
- throw e;
- } finally {
- if (socket != null) {
- socket.close();
- }
- }
- }
-
- private ServerSocket mitmBind() throws Exception {
- Log.i(TAG, "MiTM binding to local port");
- return ServerSocketFactory.getDefault().createServerSocket(0);
- }
-
- /**
- * Accepts the connection on the MiTM listening socket, forwards the TLS records between the
- * client and the server, and, if requested, injects a {@code HeartbeatRequest}.
- *
- * @param injectHeartbeat whether to inject a {@code HeartbeatRequest} message.
- * @param injectIntoClient when {@code injectHeartbeat} is {@code true}, whether to inject the
- * {@code HeartbeatRequest} message into client or into server.
- */
- private void mitmAcceptAndForward(
- SocketAddress serverAddress,
- final boolean injectHeartbeat,
- final boolean injectIntoClient) throws Exception {
- Socket clientSocket = null;
- Socket serverSocket = null;
- ServerSocket listeningSocket = getMitmListeningSocket();
- try {
- Log.i(TAG, "MiTM waiting for incoming connection");
- clientSocket = listeningSocket.accept();
- setMitmClientSocket(clientSocket);
- Log.i(TAG, "MiTM accepted connection from " + clientSocket.getRemoteSocketAddress());
- serverSocket = SocketFactory.getDefault().createSocket();
- setMitmServerSocket(serverSocket);
- Log.i(TAG, "MiTM connecting to server " + serverAddress);
- serverSocket.connect(serverAddress, 10000);
- Log.i(TAG, "MiTM connected to server from " + serverSocket.getLocalSocketAddress());
- final InputStream serverInputStream = serverSocket.getInputStream();
- final OutputStream clientOutputStream = clientSocket.getOutputStream();
- Future<Void> serverToClientTask = mExecutorService.submit(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- // Inject HeatbeatRequest after ServerHello, if requested
- forwardTlsRecords(
- "MiTM S->C",
- serverInputStream,
- clientOutputStream,
- (injectHeartbeat && injectIntoClient)
- ? HandshakeMessage.TYPE_SERVER_HELLO : -1);
- return null;
- }
- });
- // Inject HeatbeatRequest after ClientKeyExchange, if requested
- forwardTlsRecords(
- "MiTM C->S",
- clientSocket.getInputStream(),
- serverSocket.getOutputStream(),
- (injectHeartbeat && !injectIntoClient)
- ? HandshakeMessage.TYPE_CLIENT_KEY_EXCHANGE : -1);
- serverToClientTask.get(10, TimeUnit.SECONDS);
- } catch (Exception e) {
- Log.w(TAG, "MiTM failed", e);
- throw e;
- } finally {
- closeQuietly(clientSocket);
- closeQuietly(serverSocket);
- }
- }
-
- /**
- * Forwards TLS records from the provided {@code InputStream} to the provided
- * {@code OutputStream}. If requested, injects a {@code HeartbeatMessage}.
- */
- private void forwardTlsRecords(
- String logPrefix,
- InputStream in,
- OutputStream out,
- int handshakeMessageTypeAfterWhichToInjectHeartbeatRequest) throws Exception {
- Log.i(TAG, logPrefix + ": record forwarding started");
- boolean interestingRecordsLogged =
- handshakeMessageTypeAfterWhichToInjectHeartbeatRequest == -1;
- try {
- TlsRecordReader reader = new TlsRecordReader(in);
- byte[] recordBytes;
- // Fragments contained in records may be encrypted after a certain point in the
- // handshake. Once they are encrypted, this MiTM cannot inspect their plaintext which.
- boolean fragmentEncryptionMayBeEnabled = false;
- while ((recordBytes = reader.readRecord()) != null) {
- TlsRecord record = TlsRecord.parse(recordBytes);
- forwardTlsRecord(logPrefix,
- recordBytes,
- record,
- fragmentEncryptionMayBeEnabled,
- out,
- interestingRecordsLogged,
- handshakeMessageTypeAfterWhichToInjectHeartbeatRequest);
- if (record.protocol == TlsProtocols.CHANGE_CIPHER_SPEC) {
- fragmentEncryptionMayBeEnabled = true;
- }
- }
- } catch (Exception e) {
- Log.w(TAG, logPrefix + ": failed", e);
- throw e;
- } finally {
- Log.d(TAG, logPrefix + ": record forwarding finished");
- }
- }
-
- private void forwardTlsRecord(
- String logPrefix,
- byte[] recordBytes,
- TlsRecord record,
- boolean fragmentEncryptionMayBeEnabled,
- OutputStream out,
- boolean interestingRecordsLogged,
- int handshakeMessageTypeAfterWhichToInjectHeartbeatRequest) throws IOException {
- // Save information about the records if its of interest to this test
- if (interestingRecordsLogged) {
- switch (record.protocol) {
- case TlsProtocols.ALERT:
- if (!fragmentEncryptionMayBeEnabled) {
- AlertMessage alert = AlertMessage.tryParse(record);
- if ((alert != null) && (alert.level == AlertMessage.LEVEL_FATAL)) {
- setFatalAlertDetected(alert.description);
- }
- }
- break;
- case TlsProtocols.HEARTBEAT:
- // When TLS records are encrypted, we cannot determine whether a
- // heartbeat is a HeartbeatResponse. In our setup, the client and the
- // server are not expected to sent HeartbeatRequests. Thus, we err on
- // the side of caution and assume that any heartbeat message sent by
- // client or server is a HeartbeatResponse.
- Log.e(TAG, logPrefix
- + ": heartbeat response detected -- vulnerable to Heartbleed");
- setHeartbeatResponseWasDetected();
- break;
- }
- }
-
- Log.i(TAG, logPrefix + ": Forwarding TLS record. "
- + getRecordInfo(record, fragmentEncryptionMayBeEnabled));
- out.write(recordBytes);
- out.flush();
-
- // Inject HeartbeatRequest, if necessary, after the specified handshake message type
- if (handshakeMessageTypeAfterWhichToInjectHeartbeatRequest != -1) {
- if ((!fragmentEncryptionMayBeEnabled) && (isHandshakeMessageType(
- record, handshakeMessageTypeAfterWhichToInjectHeartbeatRequest))) {
- // The Heartbeat Request message below is malformed because its declared
- // length of payload one byte larger than the actual payload. The peer is
- // supposed to reject such messages.
- byte[] payload = "arbitrary".getBytes("US-ASCII");
- byte[] heartbeatRequestRecordBytes = createHeartbeatRequestRecord(
- record.versionMajor,
- record.versionMinor,
- payload.length + 1,
- payload);
- Log.i(TAG, logPrefix + ": Injecting malformed HeartbeatRequest: "
- + getRecordInfo(
- TlsRecord.parse(heartbeatRequestRecordBytes), false));
- setHeartbeatRequestWasInjected();
- out.write(heartbeatRequestRecordBytes);
- out.flush();
- }
- }
- }
-
- private static String getRecordInfo(TlsRecord record, boolean mayBeEncrypted) {
- StringBuilder result = new StringBuilder();
- result.append(getProtocolName(record.protocol))
- .append(", ")
- .append(getFragmentInfo(record, mayBeEncrypted));
- return result.toString();
- }
-
- private static String getProtocolName(int protocol) {
- switch (protocol) {
- case TlsProtocols.ALERT:
- return "alert";
- case TlsProtocols.APPLICATION_DATA:
- return "application data";
- case TlsProtocols.CHANGE_CIPHER_SPEC:
- return "change cipher spec";
- case TlsProtocols.HANDSHAKE:
- return "handshake";
- case TlsProtocols.HEARTBEAT:
- return "heatbeat";
- default:
- return String.valueOf(protocol);
- }
- }
-
- private static String getFragmentInfo(TlsRecord record, boolean mayBeEncrypted) {
- StringBuilder result = new StringBuilder();
- if (mayBeEncrypted) {
- result.append("encrypted?");
- } else {
- switch (record.protocol) {
- case TlsProtocols.ALERT:
- result.append("level: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a")
- + ", description: "
- + ((record.fragment.length > 1)
- ? String.valueOf(record.fragment[1] & 0xff) : "n/a"));
- break;
- case TlsProtocols.APPLICATION_DATA:
- break;
- case TlsProtocols.CHANGE_CIPHER_SPEC:
- result.append("payload: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
- break;
- case TlsProtocols.HANDSHAKE:
- result.append("type: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a"));
- break;
- case TlsProtocols.HEARTBEAT:
- result.append("type: " + ((record.fragment.length > 0)
- ? String.valueOf(record.fragment[0] & 0xff) : "n/a")
- + ", payload length: "
- + ((record.fragment.length >= 3)
- ? String.valueOf(
- getUnsignedShortBigEndian(record.fragment, 1))
- : "n/a"));
- break;
- }
- }
- result.append(", ").append("fragment length: " + record.fragment.length);
- return result.toString();
- }
-
- private synchronized void setServerListeningSocket(SSLServerSocket socket) {
- mServerListeningSocket = socket;
- }
-
- private synchronized SSLServerSocket getServerListeningSocket() {
- return mServerListeningSocket;
- }
-
- private synchronized void setServerSocket(SSLSocket socket) {
- mServerSocket = socket;
- }
-
- private synchronized SSLSocket getServerSocket() {
- return mServerSocket;
- }
-
- private synchronized void setClientSocket(SSLSocket socket) {
- mClientSocket = socket;
- }
-
- private synchronized SSLSocket getClientSocket() {
- return mClientSocket;
- }
-
- private synchronized void setMitmListeningSocket(ServerSocket socket) {
- mMitmListeningSocket = socket;
- }
-
- private synchronized ServerSocket getMitmListeningSocket() {
- return mMitmListeningSocket;
- }
-
- private synchronized void setMitmServerSocket(Socket socket) {
- mMitmServerSocket = socket;
- }
-
- private synchronized Socket getMitmServerSocket() {
- return mMitmServerSocket;
- }
-
- private synchronized void setMitmClientSocket(Socket socket) {
- mMitmClientSocket = socket;
- }
-
- private synchronized Socket getMitmClientSocket() {
- return mMitmClientSocket;
- }
-
- private synchronized void setHeartbeatRequestWasInjected() {
- mHeartbeatRequestWasInjected = true;
- }
-
- private synchronized boolean wasHeartbeatRequestInjected() {
- return mHeartbeatRequestWasInjected;
- }
-
- private synchronized void setHeartbeatResponseWasDetected() {
- mHeartbeatResponseWasDetetected = true;
- }
-
- private synchronized boolean wasHeartbeatResponseDetected() {
- return mHeartbeatResponseWasDetetected;
- }
-
- private synchronized void setFatalAlertDetected(int description) {
- if (mFirstDetectedFatalAlertDescription == -1) {
- mFirstDetectedFatalAlertDescription = description;
- }
- }
-
- private synchronized int getFirstDetectedFatalAlertDescription() {
- return mFirstDetectedFatalAlertDescription;
- }
-
- public static abstract class TlsProtocols {
- public static final int CHANGE_CIPHER_SPEC = 20;
- public static final int ALERT = 21;
- public static final int HANDSHAKE = 22;
- public static final int APPLICATION_DATA = 23;
- public static final int HEARTBEAT = 24;
- private TlsProtocols() {}
- }
-
- public static class TlsRecord {
- public int protocol;
- public int versionMajor;
- public int versionMinor;
- public byte[] fragment;
-
- public static TlsRecord parse(byte[] record) throws IOException {
- TlsRecord result = new TlsRecord();
- if (record.length < TlsRecordReader.RECORD_HEADER_LENGTH) {
- throw new IOException("Record too short: " + record.length);
- }
- result.protocol = record[0] & 0xff;
- result.versionMajor = record[1] & 0xff;
- result.versionMinor = record[2] & 0xff;
- int fragmentLength = getUnsignedShortBigEndian(record, 3);
- int actualFragmentLength = record.length - TlsRecordReader.RECORD_HEADER_LENGTH;
- if (fragmentLength != actualFragmentLength) {
- throw new IOException("Fragment length mismatch. Expected: " + fragmentLength
- + ", actual: " + actualFragmentLength);
- }
- result.fragment = new byte[fragmentLength];
- System.arraycopy(
- record, TlsRecordReader.RECORD_HEADER_LENGTH,
- result.fragment, 0,
- fragmentLength);
- return result;
- }
-
- public static byte[] unparse(TlsRecord record) {
- byte[] result = new byte[TlsRecordReader.RECORD_HEADER_LENGTH + record.fragment.length];
- result[0] = (byte) record.protocol;
- result[1] = (byte) record.versionMajor;
- result[2] = (byte) record.versionMinor;
- putUnsignedShortBigEndian(result, 3, record.fragment.length);
- System.arraycopy(
- record.fragment, 0,
- result, TlsRecordReader.RECORD_HEADER_LENGTH,
- record.fragment.length);
- return result;
- }
- }
-
- public static final boolean isHandshakeMessageType(TlsRecord record, int type) {
- HandshakeMessage handshake = HandshakeMessage.tryParse(record);
- if (handshake == null) {
- return false;
- }
- return handshake.type == type;
- }
-
- public static class HandshakeMessage {
- public static final int TYPE_SERVER_HELLO = 2;
- public static final int TYPE_CERTIFICATE = 11;
- public static final int TYPE_CLIENT_KEY_EXCHANGE = 16;
-
- public int type;
-
- /**
- * Parses the provided TLS record as a handshake message.
- *
- * @return alert message or {@code null} if the record does not contain a handshake message.
- */
- public static HandshakeMessage tryParse(TlsRecord record) {
- if (record.protocol != TlsProtocols.HANDSHAKE) {
- return null;
- }
- if (record.fragment.length < 1) {
- return null;
- }
- HandshakeMessage result = new HandshakeMessage();
- result.type = record.fragment[0] & 0xff;
- return result;
- }
- }
-
- public static class AlertMessage {
- public static final int LEVEL_FATAL = 2;
- public static final int DESCRIPTION_UNEXPECTED_MESSAGE = 10;
-
- public int level;
- public int description;
-
- /**
- * Parses the provided TLS record as an alert message.
- *
- * @return alert message or {@code null} if the record does not contain an alert message.
- */
- public static AlertMessage tryParse(TlsRecord record) {
- if (record.protocol != TlsProtocols.ALERT) {
- return null;
- }
- if (record.fragment.length < 2) {
- return null;
- }
- AlertMessage result = new AlertMessage();
- result.level = record.fragment[0] & 0xff;
- result.description = record.fragment[1] & 0xff;
- return result;
- }
- }
-
- private static abstract class HeartbeatProtocol {
- private HeartbeatProtocol() {}
-
- private static final int MESSAGE_TYPE_REQUEST = 1;
- @SuppressWarnings("unused")
- private static final int MESSAGE_TYPE_RESPONSE = 2;
-
- private static final int MESSAGE_HEADER_LENGTH = 3;
- private static final int MESSAGE_PADDING_LENGTH = 16;
- }
-
- private static byte[] createHeartbeatRequestRecord(
- int versionMajor, int versionMinor,
- int declaredPayloadLength, byte[] payload) {
-
- byte[] fragment = new byte[HeartbeatProtocol.MESSAGE_HEADER_LENGTH
- + payload.length + HeartbeatProtocol.MESSAGE_PADDING_LENGTH];
- fragment[0] = HeartbeatProtocol.MESSAGE_TYPE_REQUEST;
- putUnsignedShortBigEndian(fragment, 1, declaredPayloadLength); // payload_length
- TlsRecord record = new TlsRecord();
- record.protocol = TlsProtocols.HEARTBEAT;
- record.versionMajor = versionMajor;
- record.versionMinor = versionMinor;
- record.fragment = fragment;
- return TlsRecord.unparse(record);
- }
-
- /**
- * Reader of TLS records.
- */
- public static class TlsRecordReader {
- private static final int MAX_RECORD_LENGTH = 16384;
- public static final int RECORD_HEADER_LENGTH = 5;
-
- private final InputStream in;
- private final byte[] buffer;
- private int firstBufferedByteOffset;
- private int bufferedByteCount;
-
- public TlsRecordReader(InputStream in) {
- this.in = in;
- buffer = new byte[MAX_RECORD_LENGTH];
- }
-
- /**
- * Reads the next TLS record.
- *
- * @return TLS record or {@code null} if EOF was encountered before any bytes of a record
- * could be read.
- */
- public byte[] readRecord() throws IOException {
- // Ensure that a TLS record header (or more) is in the buffer.
- if (bufferedByteCount < RECORD_HEADER_LENGTH) {
- boolean eofPermittedInstead = (bufferedByteCount == 0);
- boolean eofEncounteredInstead =
- !readAtLeast(RECORD_HEADER_LENGTH, eofPermittedInstead);
- if (eofEncounteredInstead) {
- // End of stream reached exactly before a TLS record start.
- return null;
- }
- }
-
- // TLS record header (or more) is in the buffer.
- // Ensure that the rest of the record is in the buffer.
- int fragmentLength = getUnsignedShortBigEndian(buffer, firstBufferedByteOffset + 3);
- int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
- if (recordLength > MAX_RECORD_LENGTH) {
- throw new IOException("TLS record too long: " + recordLength);
- }
- if (bufferedByteCount < recordLength) {
- readAtLeast(recordLength - bufferedByteCount, false);
- }
-
- // TLS record (or more) is in the buffer.
- byte[] record = new byte[recordLength];
- System.arraycopy(buffer, firstBufferedByteOffset, record, 0, recordLength);
- firstBufferedByteOffset += recordLength;
- bufferedByteCount -= recordLength;
- return record;
- }
-
- /**
- * Reads at least the specified number of bytes from the underlying {@code InputStream} into
- * the {@code buffer}.
- *
- * <p>Bytes buffered but not yet returned to the client in the {@code buffer} are relocated
- * to the start of the buffer to make space if necessary.
- *
- * @param eofPermittedInstead {@code true} if it's permitted for an EOF to be encountered
- * without any bytes having been read.
- *
- * @return {@code true} if the requested number of bytes (or more) has been read,
- * {@code false} if {@code eofPermittedInstead} was {@code true} and EOF was
- * encountered when no bytes have yet been read.
- */
- private boolean readAtLeast(int size, boolean eofPermittedInstead) throws IOException {
- ensureRemainingBufferCapacityAtLeast(size);
- boolean firstAttempt = true;
- while (size > 0) {
- int chunkSize = in.read(
- buffer,
- firstBufferedByteOffset + bufferedByteCount,
- buffer.length - (firstBufferedByteOffset + bufferedByteCount));
- if (chunkSize == -1) {
- if ((firstAttempt) && (eofPermittedInstead)) {
- return false;
- } else {
- throw new EOFException("Premature EOF");
- }
- }
- firstAttempt = false;
- bufferedByteCount += chunkSize;
- size -= chunkSize;
- }
- return true;
- }
-
- /**
- * Ensures that there is enough capacity in the buffer to store the specified number of
- * bytes at the {@code firstBufferedByteOffset + bufferedByteCount} offset.
- */
- private void ensureRemainingBufferCapacityAtLeast(int size) throws IOException {
- int bufferCapacityRemaining =
- buffer.length - (firstBufferedByteOffset + bufferedByteCount);
- if (bufferCapacityRemaining >= size) {
- return;
- }
- // Insufficient capacity at the end of the buffer.
- if (firstBufferedByteOffset > 0) {
- // Some of the bytes at the start of the buffer have already been returned to the
- // client of this reader. Check if moving the remaining buffered bytes to the start
- // of the buffer will make enough space at the end of the buffer.
- bufferCapacityRemaining += firstBufferedByteOffset;
- if (bufferCapacityRemaining >= size) {
- System.arraycopy(buffer, firstBufferedByteOffset, buffer, 0, bufferedByteCount);
- firstBufferedByteOffset = 0;
- return;
- }
- }
-
- throw new IOException("Insuffucient remaining capacity in the buffer. Requested: "
- + size + ", remaining: " + bufferCapacityRemaining);
- }
- }
-
- private static int getUnsignedShortBigEndian(byte[] buf, int offset) {
- return ((buf[offset] & 0xff) << 8) | (buf[offset + 1] & 0xff);
- }
-
- private static void putUnsignedShortBigEndian(byte[] buf, int offset, int value) {
- buf[offset] = (byte) ((value >>> 8) & 0xff);
- buf[offset + 1] = (byte) (value & 0xff);
- }
-
- // IMPLEMENTATION NOTE: We can't implement just one closeQueietly(Closeable) because on some
- // older Android platforms Socket did not implement these interfaces. To make this patch easy to
- // apply to these older platforms, we declare all the variants of closeQuietly that are needed
- // without relying on the Closeable interface.
-
- private static void closeQuietly(InputStream in) {
- if (in != null) {
- try {
- in.close();
- } catch (IOException ignored) {}
- }
- }
-
- public static void closeQuietly(ServerSocket socket) {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException ignored) {}
- }
- }
-
- public static void closeQuietly(Socket socket) {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException ignored) {}
- }
- }
-
- public static byte[] readResource(Context context, int resId) throws IOException {
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- InputStream in = null;
- byte[] buf = new byte[16 * 1024];
- try {
- in = context.getResources().openRawResource(resId);
- int chunkSize;
- while ((chunkSize = in.read(buf)) != -1) {
- result.write(buf, 0, chunkSize);
- }
- return result.toByteArray();
- } finally {
- closeQuietly(in);
- }
- }
-
- /**
- * {@link X509TrustManager} which trusts all certificate chains.
- */
- public static class TrustAllX509TrustManager implements X509TrustManager {
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String authType)
- throws CertificateException {
- }
-
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String authType)
- throws CertificateException {
- }
-
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
- }
- }
-
- /**
- * {@link X509KeyManager} which uses the provided private key and cert chain for all sockets.
- */
- public static class HardcodedCertX509KeyManager implements X509KeyManager {
-
- private final PrivateKey mPrivateKey;
- private final X509Certificate[] mCertChain;
-
- HardcodedCertX509KeyManager(PrivateKey privateKey, X509Certificate[] certChain) {
- mPrivateKey = privateKey;
- mCertChain = certChain;
- }
-
- @Override
- public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
- return null;
- }
-
- @Override
- public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
- return "singleton";
- }
-
- @Override
- public X509Certificate[] getCertificateChain(String alias) {
- return mCertChain;
- }
-
- @Override
- public String[] getClientAliases(String keyType, Principal[] issuers) {
- return null;
- }
-
- @Override
- public PrivateKey getPrivateKey(String alias) {
- return mPrivateKey;
- }
-
- @Override
- public String[] getServerAliases(String keyType, Principal[] issuers) {
- return new String[] {"singleton"};
- }
- }
-}
diff --git a/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
new file mode 100644
index 0000000..d1b263f
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.os.Parcel;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Size;
+import android.view.Surface;
+import android.view.TextureView;
+
+/**
+ * Verify that OutputConfiguration's fields propagate through parcel properly.
+ */
+@SecurityTest
+public class OutputConfigurationTest extends AndroidTestCase {
+ public void testSharedSurfaceOutputConfigurationBasic() throws Exception {
+ SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
+ Surface surface = new Surface(outputTexture);
+
+ //Test OutputConfiguration with a surface.
+ OutputConfiguration outputConfig = new OutputConfiguration(surface);
+ outputConfig.enableSurfaceSharing();
+ Parcel p;
+ p = Parcel.obtain();
+ outputConfig.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ OutputConfiguration parcelledOutput = OutputConfiguration.CREATOR.createFromParcel(p);
+ assertEquals("Number of surfaces should be 1",
+ parcelledOutput.getSurfaces().size(),
+ 1);
+ assertEquals("Number of surfaces shouldn't change",
+ parcelledOutput.getSurfaces().size(),
+ outputConfig.getSurfaces().size());
+ assertEquals("surfaceGroupId shouldn't change",
+ parcelledOutput.getSurfaceGroupId(),
+ outputConfig.getSurfaceGroupId());
+ // addSurface shouldn't throw exception because surface sharing is enabled.
+ SurfaceTexture outputTexture2 = new SurfaceTexture(/* random texture ID */ 6);
+ Surface surface2 = new Surface(outputTexture2);
+ parcelledOutput.addSurface(surface2);
+ p.recycle();
+
+ //Test OutputConfiguration with surface group id.
+ outputConfig = new OutputConfiguration(
+ /* random surface groupd id */1, surface);
+ p = Parcel.obtain();
+ outputConfig.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ parcelledOutput = OutputConfiguration.CREATOR.createFromParcel(p);
+ assertEquals("Number of surfaces should be 1",
+ parcelledOutput.getSurfaces().size(),
+ 1);
+ assertEquals("Number of surfaces shouldn't change",
+ parcelledOutput.getSurfaces().size(),
+ outputConfig.getSurfaces().size());
+ assertEquals("surfaceGroupdId shouldn't change",
+ parcelledOutput.getSurfaceGroupId(),
+ outputConfig.getSurfaceGroupId());
+ try {
+ parcelledOutput.addSurface(surface);
+ fail("should get IllegalStateException due to OutputConfiguration not shared");
+ } catch (IllegalStateException e) {
+ // expected exception
+ }
+ p.recycle();
+
+ //Test OutputConfiguration with deferred surface.
+ Size surfaceSize = new Size(10, 10);
+ outputConfig = new OutputConfiguration(
+ surfaceSize, SurfaceTexture.class);
+ p = Parcel.obtain();
+ outputConfig.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ parcelledOutput = OutputConfiguration.CREATOR.createFromParcel(p);
+ assertEquals("Number of surfaces should be 0",
+ parcelledOutput.getSurfaces().size(), 0);
+ assertEquals("Number of surfaces shouldn't change",
+ parcelledOutput.getSurfaces().size(),
+ outputConfig.getSurfaces().size());
+ assertEquals("surfaceGroupdId shouldn't change",
+ parcelledOutput.getSurfaceGroupId(),
+ outputConfig.getSurfaceGroupId());
+ p.recycle();
+ }
+}
diff --git a/tests/tests/security/src/android/security/cts/SeccompTest.java b/tests/tests/security/src/android/security/cts/SeccompTest.java
deleted file mode 100644
index 745aa87..0000000
--- a/tests/tests/security/src/android/security/cts/SeccompTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.test.AndroidTestCase;
-
-import com.android.compatibility.common.util.CpuFeatures;
-
-import junit.framework.TestCase;
-
-/**
- * Verify that the seccomp policy is enforced
- */
-public class SeccompTest extends AndroidTestCase {
-
- static {
- System.loadLibrary("ctssecurity_jni");
- }
-
- public void testCTSSyscallBlocked() {
- if (CpuFeatures.isArm64Cpu()) {
- testBlocked(217); // __NR_add_key
- testBlocked(219); // __NR_keyctl
- testAllowed(56); // __NR_openat
-
- // b/35034743 - do not remove test without reading bug
- testAllowed(267); // __NR_fstatfs64
- } else if (CpuFeatures.isArmCpu()) {
- testBlocked(309); // __NR_add_key
- testBlocked(311); // __NR_keyctl
- testAllowed(322); // __NR_openat
-
- // b/35906875 - do not remove test without reading bug
- testAllowed(316); // __NR_inotify_init
- } else if (CpuFeatures.isX86_64Cpu()) {
- testBlocked(248); // __NR_add_key
- testBlocked(250); // __NR_keyctl
- testAllowed(257); // __NR_openat
- } else if (CpuFeatures.isX86Cpu()) {
- testBlocked(286); // __NR_add_key
- testBlocked(288); // __NR_keyctl
- testAllowed(295); // __NR_openat
- } else if (CpuFeatures.isMips64Cpu()) {
- testBlocked(5239); // __NR_add_key
- testBlocked(5241); // __NR_keyctl
- testAllowed(5247); // __NR_openat
- } else if (CpuFeatures.isMipsCpu()) {
- testBlocked(4280); // __NR_add_key
- testBlocked(4282); // __NR_keyctl
- testAllowed(4288); // __NR_openat
- } else {
- fail("Unsupported OS");
- }
- }
-
- public void testCTSSwapOnOffBlocked() {
- if (CpuFeatures.isArm64Cpu()) {
- testBlocked(224); // __NR_swapon
- testBlocked(225); // __NR_swapoff
- } else if (CpuFeatures.isArmCpu()) {
- testBlocked(87); // __NR_swapon
- testBlocked(115); // __NR_swapoff
- } else if (CpuFeatures.isX86_64Cpu()) {
- testBlocked(167); // __NR_swapon
- testBlocked(168); // __NR_swapoff
- } else if (CpuFeatures.isX86Cpu()) {
- testBlocked(87); // __NR_swapon
- testBlocked(115); // __NR_swapoff
- } else if (CpuFeatures.isMips64Cpu()) {
- testBlocked(5162); // __NR_swapon
- testBlocked(5163); // __NR_swapoff
- } else if (CpuFeatures.isMipsCpu()) {
- testBlocked(4087); // __NR_swapon
- testBlocked(4115); // __NR_swapoff
- } else {
- fail("Unsupported OS");
- }
- }
-
- private void testBlocked(int nr) {
- assertTrue("Syscall " + nr + " allowed", testSyscallBlocked(nr));
- }
-
- private void testAllowed(int nr) {
- assertFalse("Syscall " + nr + " blocked", testSyscallBlocked(nr));
- }
-
- private static final native boolean testSyscallBlocked(int nr);
-}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 87f1c0b..fb493dc 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -45,8 +45,11 @@
import android.view.Surface;
import android.webkit.cts.CtsTestServer;
+import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
@@ -436,12 +439,38 @@
CtsTestServer server = new CtsTestServer(context);
String rname = resources.getResourceEntryName(rid);
String url = server.getAssetUrl("raw/" + rname);
+ verifyServer(rid, url);
doStagefrightTestMediaPlayer(url);
doStagefrightTestMediaCodec(url);
doStagefrightTestMediaMetadataRetriever(url);
server.shutdown();
}
+ // verify that CtsTestServer is functional by retrieving the asset
+ // and comparing it to the resource
+ private void verifyServer(final int rid, final String uri) throws Exception {
+ Log.i(TAG, "checking server");
+ URL url = new URL(uri);
+ InputStream in1 = new BufferedInputStream(url.openStream());
+
+ AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
+ .openRawResourceFd(rid);
+ InputStream in2 = new BufferedInputStream(fd.createInputStream());
+
+ while (true) {
+ int b1 = in1.read();
+ int b2 = in2.read();
+ assertEquals("CtsTestServer fail", b1, b2);
+ if (b1 < 0) {
+ break;
+ }
+ }
+
+ in1.close();
+ in2.close();
+ Log.i(TAG, "checked server");
+ }
+
private void doStagefrightTestANR(final int rid) throws Exception {
doStagefrightTestMediaPlayerANR(rid, null);
}
diff --git a/tests/tests/selinux/selinuxTargetSdk/Android.mk b/tests/tests/selinux/selinuxTargetSdk/Android.mk
index a0923ee..13f1ac9 100755
--- a/tests/tests/selinux/selinuxTargetSdk/Android.mk
+++ b/tests/tests/selinux/selinuxTargetSdk/Android.mk
@@ -21,7 +21,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
ctstestrunner
-LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdkTestCases
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/selinux/selinuxTargetSdk2/Android.mk b/tests/tests/selinux/selinuxTargetSdk2/Android.mk
index f475f9f..45d570e 100755
--- a/tests/tests/selinux/selinuxTargetSdk2/Android.mk
+++ b/tests/tests/selinux/selinuxTargetSdk2/Android.mk
@@ -20,8 +20,8 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsSelinuxTargetSdk2TestCases
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/shortcutmanager/Android.mk b/tests/tests/shortcutmanager/Android.mk
index 29333aa..d9328b2 100755
--- a/tests/tests/shortcutmanager/Android.mk
+++ b/tests/tests/shortcutmanager/Android.mk
@@ -29,7 +29,7 @@
ub-uiautomator \
ShortcutManagerTestUtils
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, common/src)
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
index 1872a7d..ec688fe 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
@@ -20,6 +20,8 @@
android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
index c43d574..90d0df5 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.content.pm.cts.shortcutmanager.packages">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
index 18a118e..a5a0060 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
@@ -20,6 +20,8 @@
android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="Launcher"
android:enabled="true">
<intent-filter>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
index ee92b8c..d2cc04a 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.content.pm.cts.shortcutmanager.packages">
<application>
+ <uses-library android:name="android.test.runner" />
+
<activity android:name="Launcher" android:enabled="true" android:exported="false">
</activity>
</application>
diff --git a/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java
index 9a2659b..727828e 100644
--- a/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java
+++ b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/ShortcutConfirmPin.java
@@ -108,6 +108,7 @@
return;
}
ReplyUtil.sendSuccessReply(this, replyAction);
+ Log.e(TAG, "Sent reply");
} catch (Exception e) {
Log.e(TAG, "Caught exception", e);
if (replyAction != null) {
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
index bab2674..56969bb 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -234,14 +234,14 @@
}
public void testSetDynamicShortcuts() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1", "title1"),
makeShortcut("s2", "title2"),
makeShortcut("s3", "title3"))));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -262,12 +262,12 @@
});
// Publish from different package.
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1x", "title1x"))));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -282,7 +282,7 @@
});
// Package 1 still has the same shortcuts.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -302,12 +302,12 @@
.isEmpty();
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s2", "title2-updated"))));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -321,11 +321,11 @@
.isEmpty();
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list()));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.isEmpty();
assertWith(getManager().getPinnedShortcuts())
@@ -335,7 +335,7 @@
});
// Package2 still has the same shortcuts.
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -361,7 +361,7 @@
getTestContext().getResources(), R.drawable.black_16x64));
final Icon icon6 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo source = makeShortcutBuilder("s1")
.setShortLabel("shortlabel")
.setLongLabel("longlabel")
@@ -392,7 +392,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -418,7 +418,7 @@
getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo source = makeShortcutBuilder("s1")
.setShortLabel("shortlabel")
.setLongLabel("longlabel")
@@ -450,7 +450,7 @@
});
// paranoid icon check
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -469,7 +469,7 @@
icon2);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -489,7 +489,7 @@
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -507,7 +507,7 @@
assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
icon4);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -525,7 +525,7 @@
assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
icon5);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -547,18 +547,18 @@
public void testSetDynamicShortcuts_wasPinned() throws Exception {
// Create s1 as a floating pinned shortcut.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1"))));
});
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s1"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeDynamicShortcuts(list("s1"));
assertWith(getManager().getDynamicShortcuts())
@@ -572,14 +572,14 @@
}
public void testAddDynamicShortcuts() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcut("s1", "title1"),
makeShortcut("s2", "title2"),
makeShortcut("s3", "title3"))));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -600,12 +600,12 @@
});
// Publish from different package.
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcut("s1x", "title1x"))));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -620,7 +620,7 @@
});
// Package 1 still has the same shortcuts.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -640,12 +640,12 @@
.isEmpty();
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcut("s2", "title2-updated"))));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -665,11 +665,11 @@
.isEmpty();
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list()));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -690,7 +690,7 @@
});
// Package2 still has the same shortcuts.
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -713,7 +713,7 @@
final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo source = makeShortcutBuilder("s1")
.setShortLabel("shortlabel")
.setLongLabel("longlabel")
@@ -744,7 +744,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -770,7 +770,7 @@
getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo source = makeShortcutBuilder("s1")
.setShortLabel("shortlabel")
.setLongLabel("longlabel")
@@ -802,7 +802,7 @@
});
// paranoid icon check
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -821,7 +821,7 @@
icon2);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -841,7 +841,7 @@
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("xxx")
@@ -863,18 +863,18 @@
public void testAddDynamicShortcuts_wasPinned() throws Exception {
// Create s1 as a floating pinned shortcut.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1"))));
});
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s1"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeDynamicShortcuts(list("s1"));
assertWith(getManager().getDynamicShortcuts())
@@ -888,13 +888,13 @@
}
public void testUpdateShortcut() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1", "1a"),
makeShortcut("s2", "2a"),
makeShortcut("s3", "3a"))));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1", "1b"),
makeShortcut("s2", "2b"),
@@ -903,21 +903,21 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s2", "s3"), getUserHandle());
getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
list("s1", "s2"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeDynamicShortcuts(list("s3"));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
getManager().removeDynamicShortcuts(list("s1"));
});
// Check the current status.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -943,7 +943,7 @@
assertWith(getManager().getManifestShortcuts())
.isEmpty();
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -971,21 +971,21 @@
});
// finally, call update.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().updateShortcuts(list(
makeShortcut("s1", "upd1a"),
makeShortcut("s2", "upd2a"),
makeShortcut("xxx") // doen't exist -> ignored.
)));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertTrue(getManager().updateShortcuts(list(
makeShortcut("s1", "upd1b"),
makeShortcut("s2", "upd2b"))));
});
// check.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1011,7 +1011,7 @@
assertWith(getManager().getManifestShortcuts())
.isEmpty();
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1047,7 +1047,7 @@
final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo source = makeShortcutBuilder("s1")
.setShortLabel("shortlabel")
.setLongLabel("longlabel")
@@ -1078,7 +1078,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
// No fields updated.
final ShortcutInfo updated = makeShortcutBuilder("s1")
.build();
@@ -1102,7 +1102,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setShortLabel("x")
.build();
@@ -1126,7 +1126,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setLongLabel("y")
.build();
@@ -1150,7 +1150,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setActivity(getActivity("Launcher2"))
.build();
@@ -1174,7 +1174,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setDisabledMessage("z")
.build();
@@ -1198,7 +1198,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setIntents(new Intent[]{new Intent("main")})
.build();
@@ -1222,7 +1222,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setExtras(makePersistableBundle("ek1", "X"))
.build();
@@ -1246,7 +1246,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setCategories(set("dog"))
.build();
@@ -1270,7 +1270,7 @@
icon1);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setIcon(icon2)
.build();
@@ -1295,7 +1295,7 @@
});
// More paranoid tests with icons.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setIcon(icon1)
.build();
@@ -1307,7 +1307,7 @@
});
// More paranoid tests with icons.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setIcon(icon3)
.build();
@@ -1318,7 +1318,7 @@
icon3);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setIcon(icon4)
.build();
@@ -1329,7 +1329,7 @@
icon4);
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo updated = makeShortcutBuilder("s1")
.setIcon(icon1)
.build();
@@ -1352,13 +1352,13 @@
}
public void testDisableAndEnableShortcut() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1", "1a"),
makeShortcut("s2", "2a"),
makeShortcut("s3", "3a"))));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1", "1b"),
makeShortcut("s2", "2b"),
@@ -1367,21 +1367,21 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s2", "s3"), getUserHandle());
getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
list("s1", "s2"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeDynamicShortcuts(list("s3"));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
getManager().removeDynamicShortcuts(list("s1"));
});
// Check the current status.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1407,7 +1407,7 @@
assertWith(getManager().getManifestShortcuts())
.isEmpty();
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1435,15 +1435,15 @@
});
// finally, call disable.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().disableShortcuts(list("s1", "s3"));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
getManager().disableShortcuts(list("s1", "s2"), "custom message");
});
// check
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1469,7 +1469,7 @@
.isEmpty();
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1496,10 +1496,10 @@
});
// try re-enable
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().enableShortcuts(list("s3"));
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1524,11 +1524,11 @@
});
// Re-publish will implicitly re-enable.
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
getManager().addDynamicShortcuts(list(makeShortcut("s2", "re-published")));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
@@ -1550,7 +1550,7 @@
}
public void testImmutableShortcuts() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_2", true);
retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
@@ -1558,11 +1558,11 @@
});
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("ms21"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_1", true);
enableManifestActivity("Launcher_manifest_2", false);
@@ -1570,7 +1570,7 @@
"Manifest shortcuts didn't show up");
});
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.isEmpty();
assertWith(getManager().getPinnedShortcuts())
@@ -1610,7 +1610,7 @@
public void testManifestDefinition() throws Exception {
final Icon iconMs21 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_2", true);
retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
@@ -1687,7 +1687,7 @@
}
public void testDynamicIntents() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo s1 = makeShortcutBuilder("s1")
.setShortLabel("shortlabel")
@@ -1754,7 +1754,7 @@
}
public void testManifestWithErrors() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_error_1", true);
enableManifestActivity("Launcher_manifest_error_2", true);
enableManifestActivity("Launcher_manifest_error_3", true);
@@ -1772,7 +1772,7 @@
}
public void testManifestDisabled() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_4a", true);
retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
@@ -1786,11 +1786,11 @@
});
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("ms41", "ms42"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_4b", true);
enableManifestActivity("Launcher_manifest_4a", false);
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java
index 0614d84..cc8df51 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerConfigActivityTest.java
@@ -42,7 +42,7 @@
public void testGetShortcutConfigActivityList() throws Exception {
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
assertNotNull(getConfigActivity());
assertNotNull(getLauncherApps().getShortcutConfigActivityIntent(getConfigActivity()));
});
@@ -50,7 +50,7 @@
// Get config activity works even for non-default activity.
setDefaultLauncher(getInstrumentation(), mLauncherContext4);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
assertNotNull(getConfigActivity());
// throws exception when default launcher is different.
assertExpectException(SecurityException.class, null, () ->
@@ -61,7 +61,7 @@
public void testCorrectIntentSenderCreated() throws Throwable {
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
final AtomicReference<IntentSender> sender = new AtomicReference<>();
- runWithCaller(mLauncherContext1, () ->
+ runWithCallerWithStrictMode(mLauncherContext1, () ->
sender.set(getLauncherApps().getShortcutConfigActivityIntent(getConfigActivity())));
Instrumentation.ActivityMonitor monitor =
@@ -87,7 +87,7 @@
public void testCreateShortcutResultIntent_defaultLauncher() throws Exception {
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
PinItemRequest request = getShortcutRequestForPackage1();
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
assertTrue(request.isValid());
assertTrue(request.accept());
});
@@ -99,7 +99,7 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext4);
// Launcher1 can still access the request
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
assertTrue(request.isValid());
assertTrue(request.accept());
});
@@ -110,7 +110,7 @@
PinItemRequest request = getShortcutRequestForPackage1();
// Launcher1 can still access the request
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
assertFalse(request.isValid());
assertExpectException(SecurityException.class, null, request::accept);
});
@@ -118,7 +118,7 @@
private PinItemRequest getShortcutRequestForPackage1() {
final AtomicReference<PinItemRequest> result = new AtomicReference<>();
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
final ShortcutInfo shortcut = makeShortcutBuilder(SHORTCUT_ID)
.setShortLabel("label1")
.setIntent(new Intent(Intent.ACTION_MAIN))
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
index 1f45a7f..aa0ea527 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -33,12 +33,13 @@
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
import android.os.UserHandle;
import android.support.annotation.NonNull;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
-import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -50,6 +51,14 @@
private static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+ /**
+ * Whether to enable strict mode or not.
+ *
+ * TODO Enable it after fixing b/68051728. Somehow violations would happen on the dashboard
+ * only and can't reproduce it locally.
+ */
+ private static final boolean ENABLE_STRICT_MODE = false;
+
private static class SpoofingContext extends ContextWrapper {
private final String mPackageName;
@@ -276,6 +285,33 @@
return mCurrentLauncherApps;
}
+ protected void runWithStrictMode(Runnable r) {
+ final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ if (ENABLE_STRICT_MODE) {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ }
+ r.run();
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ protected void runWithNoStrictMode(Runnable r) {
+ final ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .permitAll()
+ .build());
+ r.run();
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
protected void runWithCaller(Context callerContext, Runnable r) {
final Context prev = mCurrentCallerPackage;
@@ -286,6 +322,14 @@
setCurrentCaller(prev);
}
+ protected void runWithCallerWithStrictMode(Context callerContext, Runnable r) {
+ runWithCaller(callerContext, () -> runWithStrictMode(r));
+ }
+
+ protected void runWithCallerWithNoStrictMode(Context callerContext, Runnable r) {
+ runWithCaller(callerContext, () -> runWithNoStrictMode(r));
+ }
+
public static Bundle makeBundle(Object... keysAndValues) {
assertTrue((keysAndValues.length % 2) == 0);
@@ -437,11 +481,11 @@
protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
String shortcutId, boolean withBadge) {
- setDefaultLauncher(getInstrumentation(), launcherContext);
+ runWithNoStrictMode(() -> setDefaultLauncher(getInstrumentation(), launcherContext));
final AtomicReference<Drawable> ret = new AtomicReference<>();
- runWithCaller(launcherContext, () -> {
+ runWithCallerWithNoStrictMode(launcherContext, () -> {
final ShortcutQuery q = new ShortcutQuery()
.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
| ShortcutQuery.FLAG_MATCH_MANIFEST
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
index e527bf9..f27a60a 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -40,7 +40,7 @@
}
public void testPinShortcuts() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_1", true);
enableManifestActivity("Launcher_manifest_2", true);
@@ -59,7 +59,7 @@
.areAllDynamic()
.areAllEnabled();
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
enableManifestActivity("Launcher_manifest_1", true);
enableManifestActivity("Launcher_manifest_3", true);
@@ -81,7 +81,7 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(
mPackageContext1.getPackageName(),
list("s1", "s2", "s3", "ms1", "ms21"), getUserHandle());
@@ -90,11 +90,11 @@
list("s2", "s3", "ms1", "ms31"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeDynamicShortcuts(list("s1", "s2"));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
enableManifestActivity("Launcher_manifest_3", false);
retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
@@ -104,7 +104,7 @@
});
// Check the result.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getDynamicShortcuts())
.haveIds("s3", "s4", "s5")
.areAllEnabled();
@@ -116,7 +116,7 @@
.areAllEnabled();
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertWith(getManager().getDynamicShortcuts())
.haveIds("s3", "s4", "s5")
.areAllEnabled();
@@ -142,7 +142,7 @@
Thread.sleep(2);
final long time1 = System.currentTimeMillis();
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().updateShortcuts(list(
makeShortcut("s3"))));
@@ -156,12 +156,12 @@
Thread.sleep(2);
final long time2 = System.currentTimeMillis();
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().updateShortcuts(list(
makeShortcutWithRank("s4", 999))));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
setTargetActivityOverride("Launcher_manifest_1");
assertTrue(getManager().updateShortcuts(list(
@@ -171,7 +171,7 @@
Thread.sleep(2);
final long time3 = System.currentTimeMillis();
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
assertWith(getShortcutsAsLauncher(
FLAG_MATCH_DYNAMIC,
mPackageContext1.getPackageName(),
@@ -311,7 +311,7 @@
final Icon icon5 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_2", true);
retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
@@ -357,7 +357,7 @@
final Icon icon2 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_2", true);
retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
index 3e75b4a..8b7ae4e 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
@@ -29,7 +29,7 @@
* Basic tests: single app, single activity, no manifest shortcuts.
*/
public void testNumDynamicShortcuts() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(makeShortcut("s1"))));
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1"),
@@ -101,7 +101,7 @@
* Manifest shortcuts are included in the count too.
*/
public void testWithManifest() throws Exception {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_1", true);
enableManifestActivity("Launcher_manifest_2", true);
@@ -110,7 +110,7 @@
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertWith(getManager().getManifestShortcuts())
.haveIds("ms1", "ms21", "ms22")
.areAllManifest()
@@ -134,7 +134,7 @@
testNumDynamicShortcuts();
// Launcher_manifest_1 has one manifest, so can only add 4 dynamic shortcuts.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
setTargetActivityOverride("Launcher_manifest_1");
assertTrue(getManager().setDynamicShortcuts(list(
@@ -163,7 +163,7 @@
});
// Launcher_manifest_2 has two manifests, so can only add 3.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
setTargetActivityOverride("Launcher_manifest_2");
assertTrue(getManager().addDynamicShortcuts(list(
@@ -188,7 +188,7 @@
}
public void testChangeActivity() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
setTargetActivityOverride("Launcher");
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1"),
@@ -262,7 +262,7 @@
}
public void testWithPinned() {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcut("s2"),
@@ -274,12 +274,12 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s1", "s2", "s3", "s4", "s5"), getUserHandle());
});
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut("s6"),
makeShortcut("s7"),
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
index 78dbccd..5ed5d52 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
@@ -16,6 +16,7 @@
package android.content.pm.cts.shortcutmanager;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
@@ -266,6 +267,18 @@
.revertToOriginalList()
.selectByIds("ms32")
.areAllDisabled();
+
+ // Make sure "ALL_PINNED" doesn't work without the permission.
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY
+ | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER,
+ mPackageContext1.getPackageName()))
+ .haveIds("s3", "s4", "ms22");
+ assertWith(getShortcutsAsLauncher(
+ FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY
+ | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER,
+ mPackageContext2.getPackageName()))
+ .haveIds("s1", "s2", "s3", "ms32");
});
}
}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
index 756cf80..c04c0af 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
@@ -40,6 +40,8 @@
public class ShortcutManagerRequestPinTest extends ShortcutManagerCtsTestsBase {
private static final String TAG = "ShortcutMRPT";
+ private static final String SHORTCUT_ID = "s12345";
+
public void testIsRequestPinShortcutSupported() {
// Launcher 1 supports it.
@@ -61,11 +63,11 @@
* A test for {@link ShortcutManager#requestPinShortcut}, a very simple case.
*/
public void testRequestPinShortcut() {
+ Log.i(TAG, "Testing with launcher1.");
+
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
- final String SHORTCUT_ID = "s12345";
-
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().isRequestPinShortcutSupported());
ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
@@ -79,12 +81,19 @@
.setExtras(extras)
.build();
+ // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+ // update the extras that contains the broadcast ID, we need to update the shortcut
+ // manually here before requestPinShortcut().
+
+ // This is only needed when a shortcut is already published with the same ID.
+ assertTrue(getManager().updateShortcuts(list(shortcut)));
+
Log.i(TAG, "Calling requestPinShortcut...");
assertTrue(getManager().requestPinShortcut(shortcut, /* intent sender */ null));
Log.i(TAG, "Done.");
});
});
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
final ShortcutQuery query = new ShortcutQuery()
.setPackage(mPackageContext1.getPackageName())
.setShortcutIds(list(SHORTCUT_ID))
@@ -117,6 +126,183 @@
.areAllMutable()
;
});
+
+ Log.i(TAG, "Done testing with launcher1.");
+ }
+
+ public void testRequestPinShortcut_multiLaunchers() {
+ testRequestPinShortcut();
+
+ Log.i(TAG, "Testing with launcher2.");
+
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
+ final PersistableBundle extras = new PersistableBundle();
+ extras.putString(Constants.EXTRA_REPLY_ACTION, replyAction);
+ extras.putString(Constants.LABEL, "label1");
+
+ final ShortcutInfo shortcut = makeShortcutBuilder(SHORTCUT_ID)
+ .setExtras(extras)
+ .build();
+
+ // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+ // update the extras that contains the broadcast ID, we need to update the shortcut
+ // manually here before requestPinShortcut().
+ assertTrue(getManager().updateShortcuts(list(shortcut)));
+
+ Log.i(TAG, "Calling requestPinShortcut...");
+ assertTrue(getManager().requestPinShortcut(shortcut, /* intent sender */ null));
+ Log.i(TAG, "Done.");
+ });
+ });
+ runWithCallerWithStrictMode(mLauncherContext2, () -> {
+ final ShortcutQuery query = new ShortcutQuery()
+ .setPackage(mPackageContext1.getPackageName())
+ .setShortcutIds(list(SHORTCUT_ID))
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_MANIFEST);
+ Log.i(TAG, "Waiting for shortcut to be visible to launcher...");
+ retryUntil(() -> {
+ final List<ShortcutInfo> shortcuts = getLauncherApps().getShortcuts(query,
+ android.os.Process.myUserHandle());
+ if (shortcuts == null) {
+ // Launcher not responded yet.
+ return false;
+ }
+ assertWith(shortcuts)
+ .haveIds(SHORTCUT_ID)
+ .areAllPinned()
+ .areAllNotDynamic()
+ .areAllNotManifest();
+ return true;
+ }, "Shortcut still not pinned");
+ });
+ Log.i(TAG, "Done testing with launcher2.");
+ }
+
+ public void testRequestPinShortcut_multiLaunchers_withDynamic() {
+ setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+ // Publish as a dynamic shortcut first, then call requestPin.
+ ShortcutInfo shortcut = makeShortcutBuilder(SHORTCUT_ID)
+ .setShortLabel("label1")
+ .setIntent(new Intent(Intent.ACTION_MAIN))
+ .build();
+ assertTrue(getManager().setDynamicShortcuts(list(shortcut)));
+
+ // ==============================================================
+ Log.i(TAG, "Testing with launcher1.");
+
+ assertTrue(getManager().isRequestPinShortcutSupported());
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
+ final PersistableBundle extras = new PersistableBundle();
+ extras.putString(Constants.EXTRA_REPLY_ACTION, replyAction);
+ extras.putString(Constants.LABEL, "label1");
+
+ final ShortcutInfo shortcut2 = makeShortcutBuilder(SHORTCUT_ID)
+ .setExtras(extras)
+ .build();
+
+ // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+ // update the extras that contains the broadcast ID, we need to update the shortcut
+ // manually here before requestPinShortcut().
+
+ // This is only needed when a shortcut is already published with the same ID.
+ assertTrue(getManager().updateShortcuts(list(shortcut2)));
+
+ Log.i(TAG, "Calling requestPinShortcut...");
+ assertTrue(getManager().requestPinShortcut(shortcut2, /* intent sender */ null));
+ Log.i(TAG, "Done.");
+ });
+ });
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
+ final ShortcutQuery query = new ShortcutQuery()
+ .setPackage(mPackageContext1.getPackageName())
+ .setShortcutIds(list(SHORTCUT_ID))
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_MANIFEST);
+ Log.i(TAG, "Waiting for shortcut to be visible to launcher...");
+ retryUntil(() -> {
+ final List<ShortcutInfo> shortcuts = getLauncherApps().getShortcuts(query,
+ android.os.Process.myUserHandle());
+ if (shortcuts == null) {
+ // Launcher not responded yet.
+ return false;
+ }
+ assertWith(shortcuts)
+ .haveIds(SHORTCUT_ID)
+ .areAllPinned()
+ .areAllDynamic()
+ .areAllNotManifest();
+ return true;
+ }, "Shortcut still not pinned");
+ });
+ // ==============================================================
+ Log.i(TAG, "Testing with launcher2.");
+ setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+ assertTrue(getManager().isRequestPinShortcutSupported());
+
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
+ final PersistableBundle extras = new PersistableBundle();
+ extras.putString(Constants.EXTRA_REPLY_ACTION, replyAction);
+ extras.putString(Constants.LABEL, "label1");
+
+ final ShortcutInfo shortcut2 = makeShortcutBuilder(SHORTCUT_ID)
+ .setExtras(extras)
+ .build();
+
+ // Note: Because requestPinShortcut() won't update the shortcut, but we need to
+ // update the extras that contains the broadcast ID, we need to update the shortcut
+ // manually here before requestPinShortcut().
+
+ // This is only needed when a shortcut is already published with the same ID.
+ assertTrue(getManager().updateShortcuts(list(shortcut2)));
+
+ Log.i(TAG, "Calling requestPinShortcut...");
+ assertTrue(getManager().requestPinShortcut(shortcut2, /* intent sender */ null));
+ Log.i(TAG, "Done.");
+ });
+ });
+ runWithCallerWithStrictMode(mLauncherContext2, () -> {
+ final ShortcutQuery query = new ShortcutQuery()
+ .setPackage(mPackageContext1.getPackageName())
+ .setShortcutIds(list(SHORTCUT_ID))
+ .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+ | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_MANIFEST);
+ Log.i(TAG, "Waiting for shortcut to be visible to launcher...");
+ retryUntil(() -> {
+ final List<ShortcutInfo> shortcuts = getLauncherApps().getShortcuts(query,
+ android.os.Process.myUserHandle());
+ if (shortcuts == null) {
+ // Launcher not responded yet.
+ return false;
+ }
+ assertWith(shortcuts)
+ .haveIds(SHORTCUT_ID)
+ .areAllPinned()
+ .areAllDynamic()
+ .areAllNotManifest();
+ return true;
+ }, "Shortcut still not pinned");
+ });
+
+ runWithCaller(mPackageContext1, () -> {
+ assertWith(getManager().getPinnedShortcuts())
+ .forShortcutWithId(SHORTCUT_ID, si -> {
+ assertEquals("label1", si.getShortLabel());
+ })
+ .areAllPinned()
+ .areAllDynamic()
+ .areAllNotManifest()
+ .areAllMutable()
+ ;
+ });
}
/**
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
index 9cf1f89..a66e8ed 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
@@ -53,7 +53,7 @@
ShortcutLaunchedActivity.setExpectedOrder(expectedActions);
- runWithCaller(launcher, () -> {
+ runWithCallerWithStrictMode(launcher, () -> {
getLauncherApps().startShortcut(client.getPackageName(), id, rect, options,
getUserHandle());
});
@@ -73,7 +73,7 @@
private void assertShortcutCantStart(Context launcher, Context client, String id,
Class<? extends Throwable> exceptionClass) {
- runWithCaller(launcher, () -> {
+ runWithCallerWithStrictMode(launcher, () -> {
assertExpectException(exceptionClass, "", () -> {
getLauncherApps().startShortcut(client.getPackageName(), id, null, null,
@@ -96,7 +96,7 @@
.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
.putExtra("k1", "v1");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcutBuilder("s1").setShortLabel("abc")
.setIntent(i).build()
@@ -128,7 +128,7 @@
.setComponent(mLaunchedActivity)
.putExtra("kx", "vx");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcutBuilder("s1").setShortLabel("abc")
.setIntents(new Intent[]{i1, i2, i3}).build()
@@ -182,7 +182,7 @@
testStartMultiple();
// then remove it.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeAllDynamicShortcuts();
});
@@ -198,7 +198,7 @@
.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
.putExtra("k1", "v1");
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcutBuilder("s1").setShortLabel("abc")
.setIntent(i).build()
@@ -219,13 +219,13 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
// then pin it.
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s1"), getUserHandle());
});
// Then remove it.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeAllDynamicShortcuts();
});
@@ -241,7 +241,7 @@
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
// then pin it.
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
list("s1"), getUserHandle());
});
@@ -257,7 +257,7 @@
assertShortcutStarts(mLauncherContext2, mPackageContext1, "s1", EXPECTED_ACTIONS_SINGLE);
// Then remove it.
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
getManager().removeAllDynamicShortcuts();
});
@@ -304,7 +304,7 @@
Intent i = new Intent(Intent.ACTION_MAIN)
.setComponent(new ComponentName("abc", "def"));
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcutBuilder("s1").setShortLabel("abc")
.setIntent(i).build()
@@ -329,7 +329,7 @@
"android.content.pm.cts.shortcutmanager.packages.package4",
"android.content.pm.cts.shortcutmanager.packages.Launcher"));
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().addDynamicShortcuts(list(
makeShortcutBuilder("s1").setShortLabel("abc")
.setIntent(i).build()
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
index 064ba58..50ce615 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
@@ -75,7 +75,7 @@
public void testReportShortcutUsed() throws InterruptedException {
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
enableManifestActivity("Launcher_manifest_2", true);
retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
@@ -89,13 +89,13 @@
final String idManifest = "ms21";
final String idNonexistance = "nonexistence";
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut(id1),
makeShortcut(id2)
)));
});
- runWithCaller(mPackageContext2, () -> {
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
makeShortcut(id1),
makeShortcut(id3)
@@ -106,7 +106,7 @@
// Report usage.
final long start1 = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
- runWithCaller(mPackageContext2, () -> getManager().reportShortcutUsed(id3));
+ runWithCallerWithStrictMode(mPackageContext2, () -> getManager().reportShortcutUsed(id3));
final long end1 = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
// Check the log.
@@ -115,7 +115,7 @@
// Report usage.
final long start2 = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
- runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(id1));
+ runWithCallerWithStrictMode(mPackageContext1, () -> getManager().reportShortcutUsed(id1));
final long end2 = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
// Check the log.
@@ -124,8 +124,8 @@
// Report usage.
final long start3 = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
- runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idNonexistance));
- runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idManifest));
+ runWithCallerWithStrictMode(mPackageContext1, () -> getManager().reportShortcutUsed(idNonexistance));
+ runWithCallerWithStrictMode(mPackageContext1, () -> getManager().reportShortcutUsed(idManifest));
final long end3 = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
// Check the log.
diff --git a/tests/tests/shortcutmanager/throttling/AndroidManifest.xml b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
index fcbc167..0d444d4 100644
--- a/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
<application>
+ <uses-library android:name="android.test.runner" />
+
<receiver
android:name="ShortcutManagerThrottlingTestReceiver"
android:exported="true">
diff --git a/tests/tests/simpleperf/AndroidTest.xml b/tests/tests/simpleperf/AndroidTest.xml
index 89faa0f..eeb6f99 100644
--- a/tests/tests/simpleperf/AndroidTest.xml
+++ b/tests/tests/simpleperf/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Simpleperf test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="bionic" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
diff --git a/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk b/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk
index 2a172b5..4f097a4 100644
--- a/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk
+++ b/tests/tests/simpleperf/CtsSimpleperfDebugApp/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, .)
diff --git a/tests/tests/slice/Android.mk b/tests/tests/slice/Android.mk
new file mode 100644
index 0000000..0e29781
--- /dev/null
+++ b/tests/tests/slice/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+ ctsdeviceutillegacy \
+ ctstestrunner \
+ mockito-target-minus-junit4 \
+ platform-test-annotations \
+ ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSliceTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/slice/AndroidManifest.xml b/tests/tests/slice/AndroidManifest.xml
new file mode 100644
index 0000000..87253c5
--- /dev/null
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.slice.cts">
+
+ <uses-permission android:name="android.permission.BIND_SLICE" />
+
+ <application android:label="Android TestCase"
+ android:icon="@drawable/size_48x48"
+ android:maxRecents="1"
+ android:multiArch="true"
+ android:supportsRtl="true">
+ <uses-library android:name="android.test.runner" />
+
+ <provider android:name=".SliceProvider"
+ android:authorities="android.slice.cts"
+ android:process=":slice_process" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.slice.cts"
+ android:label="CTS tests of android.slice">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/tests/slice/AndroidTest.xml b/tests/tests/slice/AndroidTest.xml
new file mode 100644
index 0000000..ceae60a
--- /dev/null
+++ b/tests/tests/slice/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Slice test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="misc" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsSliceTestCases.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="pm grant android.slice.cts android.permission.BIND_SLICE" />
+ <option name="teardown-command" value="pm revoke android.slice.cts android.permission.BIND_SLICE"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.slice.cts" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/tests/slice/res/drawable/size_48x48.jpg b/tests/tests/slice/res/drawable/size_48x48.jpg
new file mode 100644
index 0000000..5c2291e
--- /dev/null
+++ b/tests/tests/slice/res/drawable/size_48x48.jpg
Binary files differ
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProvider.java b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
new file mode 100644
index 0000000..393af18
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.slice.cts;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.app.slice.Slice;
+import android.app.slice.Slice.Builder;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class SliceProvider extends android.app.slice.SliceProvider {
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Slice onBindSlice(Uri sliceUri) {
+ switch (sliceUri.getPath()) {
+ case "/set_flag":
+ SliceTest.sFlag = true;
+ break;
+ case "/subslice":
+ Builder b = new Builder(sliceUri);
+ return b.addSubSlice(new Slice.Builder(b).build(), "subslice").build();
+ case "/text":
+ return new Slice.Builder(sliceUri).addText("Expected text", "text").build();
+ case "/icon":
+ return new Slice.Builder(sliceUri).addIcon(
+ Icon.createWithResource(getContext(), R.drawable.size_48x48), "icon").build();
+ case "/action":
+ Builder builder = new Builder(sliceUri);
+ Slice subSlice = new Slice.Builder(builder).build();
+ PendingIntent broadcast = PendingIntent.getBroadcast(getContext(), 0,
+ new Intent(getContext().getPackageName() + ".action"), 0);
+ return builder.addAction(broadcast, subSlice, "action").build();
+ case "/int":
+ return new Slice.Builder(sliceUri).addInt(0xff121212, "int").build();
+ case "/timestamp":
+ return new Slice.Builder(sliceUri).addTimestamp(43, "timestamp").build();
+ case "/hints":
+ return new Slice.Builder(sliceUri)
+ .addHints(Slice.HINT_LIST)
+ .addText("Text", null, Slice.HINT_TITLE)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.size_48x48),
+ null, Slice.HINT_NO_TINT, Slice.HINT_LARGE)
+ .build();
+ case "/bundle":
+ Bundle b1 = new Bundle();
+ b1.putParcelable("a", new TestParcel());
+ return new Slice.Builder(sliceUri).addBundle(b1, "bundle").build();
+ }
+ return new Slice.Builder(sliceUri).build();
+ }
+
+ public static class TestParcel implements Parcelable {
+
+ private final int mValue;
+
+ public TestParcel() {
+ mValue = 42;
+ }
+
+ protected TestParcel(Parcel in) {
+ mValue = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mValue);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ try {
+ TestParcel p = (TestParcel) obj;
+ return p.mValue == mValue;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public static final Creator<TestParcel> CREATOR = new Creator<TestParcel>() {
+ @Override
+ public TestParcel createFromParcel(Parcel in) {
+ return new TestParcel(in);
+ }
+
+ @Override
+ public TestParcel[] newArray(int size) {
+ return new TestParcel[size];
+ }
+ };
+ }
+}
diff --git a/tests/tests/slice/src/android/slice/cts/SliceTest.java b/tests/tests/slice/src/android/slice/cts/SliceTest.java
new file mode 100644
index 0000000..6b3ee00
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.slice.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.os.Bundle;
+import android.slice.cts.SliceProvider.TestParcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SliceTest {
+
+ public static boolean sFlag = false;
+
+ private static final Uri BASE_URI = Uri.parse("content://android.slice.cts/");
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ @Test
+ public void testProcess() {
+ sFlag = false;
+ Slice.bindSlice(mContext.getContentResolver(),
+ BASE_URI.buildUpon().appendPath("set_flag").build(),
+ Collections.emptyList());
+ assertFalse(sFlag);
+ }
+
+ @Test
+ public void testType() {
+ assertEquals(SliceProvider.SLICE_TYPE,
+ mContext.getContentResolver().getType(BASE_URI));
+ }
+
+ @Test
+ public void testSliceUri() {
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), BASE_URI,
+ Collections.emptyList());
+ assertEquals(BASE_URI, s.getUri());
+ }
+
+ @Test
+ public void testSubSlice() {
+ Uri uri = BASE_URI.buildUpon().appendPath("subslice").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_SLICE, item.getFormat());
+ assertEquals("subslice", item.getSubType());
+ // The item should start with the same Uri as the parent, but be different.
+ assertTrue(item.getSlice().getUri().toString().startsWith(uri.toString()));
+ assertNotEquals(uri, item.getSlice().getUri());
+ }
+
+ @Test
+ public void testText() {
+ Uri uri = BASE_URI.buildUpon().appendPath("text").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_TEXT, item.getFormat());
+ // TODO: Test spannables here.
+ assertEquals("Expected text", item.getText());
+ }
+
+ @Test
+ public void testIcon() {
+ Uri uri = BASE_URI.buildUpon().appendPath("icon").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_IMAGE, item.getFormat());
+ assertEquals(Icon.createWithResource(mContext, R.drawable.size_48x48).toString(),
+ item.getIcon().toString());
+ }
+
+ @Test
+ public void testAction() {
+ sFlag = false;
+ CountDownLatch latch = new CountDownLatch(1);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sFlag = true;
+ latch.countDown();
+ }
+ };
+ mContext.registerReceiver(receiver,
+ new IntentFilter(mContext.getPackageName() + ".action"));
+ Uri uri = BASE_URI.buildUpon().appendPath("action").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_ACTION, item.getFormat());
+ try {
+ item.getAction().send();
+ } catch (CanceledException e) {
+ }
+
+ try {
+ latch.await(100, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ assertTrue(sFlag);
+ mContext.unregisterReceiver(receiver);
+ }
+
+ @Test
+ public void testInt() {
+ Uri uri = BASE_URI.buildUpon().appendPath("int").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_INT, item.getFormat());
+ assertEquals(0xff121212, item.getInt());
+ }
+
+ @Test
+ public void testTimestamp() {
+ Uri uri = BASE_URI.buildUpon().appendPath("timestamp").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_TIMESTAMP, item.getFormat());
+ assertEquals(43, item.getTimestamp());
+ }
+
+ @Test
+ public void testHints() {
+ // Note this tests that hints are propagated through to the client but not that any specific
+ // hints have any effects.
+ Uri uri = BASE_URI.buildUpon().appendPath("hints").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+
+ assertEquals(Arrays.asList(Slice.HINT_LIST), s.getHints());
+ assertEquals(Arrays.asList(Slice.HINT_TITLE), s.getItems().get(0).getHints());
+ assertEquals(Arrays.asList(Slice.HINT_NO_TINT, Slice.HINT_LARGE),
+ s.getItems().get(1).getHints());
+ }
+
+ @Test
+ public void testBundle() {
+ Uri uri = BASE_URI.buildUpon().appendPath("bundle").build();
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri,
+ Collections.emptyList());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+
+ SliceItem item = s.getItems().get(0);
+ assertEquals(SliceItem.FORMAT_BUNDLE, item.getFormat());
+ Bundle b = item.getBundle();
+ b.setClassLoader(getClass().getClassLoader());
+ assertEquals(new TestParcel(), b.getParcelable("a"));
+ }
+}
diff --git a/tests/tests/speech/Android.mk b/tests/tests/speech/Android.mk
index 05f9388..1093467 100755
--- a/tests/tests/speech/Android.mk
+++ b/tests/tests/speech/Android.mk
@@ -23,8 +23,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- android-support-test \
- legacy-android-test
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/speech/AndroidTest.xml b/tests/tests/speech/AndroidTest.xml
index 9cc3132..adc4fab 100644
--- a/tests/tests/speech/AndroidTest.xml
+++ b/tests/tests/speech/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Speech test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
index 857ffd8..6b3abf6 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
@@ -15,6 +15,7 @@
*/
package android.speech.tts.cts;
+import android.os.ConditionVariable;
import android.media.AudioFormat;
import android.os.ConditionVariable;
import android.speech.tts.SynthesisCallback;
@@ -37,6 +38,10 @@
// Object that onSynthesizeText will #block on, if set to non-null
public static volatile ConditionVariable sSynthesizeTextWait;
+ // Condition variable that onSynthesizeText will #open when it started
+ // synethesizing, if set to non-null.
+ public static volatile ConditionVariable sSynthesizeTextStartEvent;
+
private ArrayList<Locale> supportedLanguages = new ArrayList<Locale>();
private ArrayList<Locale> supportedCountries = new ArrayList<Locale>();
private ArrayList<Locale> GBFallbacks = new ArrayList<Locale>();
@@ -80,6 +85,11 @@
return;
}
+ final ConditionVariable synthesizeTextStartEvent = sSynthesizeTextStartEvent;
+ if (synthesizeTextStartEvent != null) {
+ sSynthesizeTextStartEvent.open();
+ }
+
final ConditionVariable synthesizeTextWait = sSynthesizeTextWait;
if (synthesizeTextWait != null) {
synthesizeTextWait.block(10000); // 10s timeout
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
index 4d9faad..8e54d31 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -117,6 +117,27 @@
}
}
+ public void testSpeakStopBehindOtherAudioPlayback() throws Exception {
+ final ConditionVariable synthesizeTextWait = new ConditionVariable();
+ final ConditionVariable synthesizeTextStartEvent = new ConditionVariable();
+ StubTextToSpeechService.sSynthesizeTextWait = synthesizeTextWait;
+ StubTextToSpeechService.sSynthesizeTextStartEvent = synthesizeTextStartEvent;
+
+ // Make the audio playback queue busy by putting a 30s of silence.
+ getTts().stop();
+ getTts().playSilentUtterance(30000, TextToSpeech.QUEUE_ADD, "silence");
+
+ // speak(), wait it to starting in the service, and stop().
+ int result = getTts().speak(UTTERANCE, TextToSpeech.QUEUE_ADD, null, "stop");
+ assertEquals("speak() failed", TextToSpeech.SUCCESS, result);
+ assertTrue("synthesis not started", synthesizeTextStartEvent.block(10000));
+ getTts().stop();
+
+ // Wake up the Stubs #onSynthesizeSpeech (one that will be stopped in-progress)
+ synthesizeTextWait.open();
+
+ assertTrue("speak() stop callback timeout", mTts.waitForStop("stop"));
+ }
public void testMediaPlayerFails() throws Exception {
File sampleFile = new File(Environment.getExternalStorageDirectory(), "notsound.wav");
diff --git a/tests/tests/systemintents/Android.mk b/tests/tests/systemintents/Android.mk
index 573e2ee..09ac3314 100644
--- a/tests/tests/systemintents/Android.mk
+++ b/tests/tests/systemintents/Android.mk
@@ -27,7 +27,7 @@
LOCAL_PACKAGE_NAME := CtsSystemIntentTestCases
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
LOCAL_SDK_VERSION := test_current
diff --git a/tests/tests/systemintents/AndroidTest.xml b/tests/tests/systemintents/AndroidTest.xml
index 37f9895..4f0cc36 100644
--- a/tests/tests/systemintents/AndroidTest.xml
+++ b/tests/tests/systemintents/AndroidTest.xml
@@ -15,6 +15,7 @@
~ limitations under the License
-->
<configuration description="Config for CTS system intent test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
index c572629..086f2cb 100644
--- a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
@@ -16,6 +16,8 @@
package android.systemintents.cts;
+import static org.junit.Assert.assertTrue;
+
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -23,14 +25,17 @@
import android.net.Uri;
import android.provider.Settings;
import android.support.test.filters.MediumTest;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
import android.util.Log;
-import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
@MediumTest
-public class TestSystemIntents extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class TestSystemIntents {
+
/*
* List of activity intents defined by the system. Activities to handle each of these
* intents must all exist.
@@ -56,7 +61,6 @@
}
}
- @Rule
private final IntentEntry[] mTestIntents = {
/* Settings-namespace intent actions */
new IntentEntry(0, new Intent(Settings.ACTION_SETTINGS)),
@@ -76,7 +80,7 @@
@Test
public void testSystemIntents() {
- final PackageManager pm = getContext().getPackageManager();
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
int productFlags = 0;
if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
@@ -87,7 +91,7 @@
productFlags |= EXCLUDE_NON_TELEPHONY;
}
- final Configuration config = getContext().getResources().getConfiguration();
+ final Configuration config = InstrumentationRegistry.getContext().getResources().getConfiguration();
if ((config.uiMode & Configuration.UI_MODE_TYPE_WATCH) != 0) {
productFlags |= EXCLUDE_WATCH;
}
@@ -99,4 +103,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/systemui/Android.mk b/tests/tests/systemui/Android.mk
index 6ba1e77..073e829 100644
--- a/tests/tests/systemui/Android.mk
+++ b/tests/tests/systemui/Android.mk
@@ -29,7 +29,6 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
android-support-test \
- legacy-android-test \
ub-uiautomator
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/telecom/Android.mk b/tests/tests/telecom/Android.mk
index 8a243eb..8f46fd7 100644
--- a/tests/tests/telecom/Android.mk
+++ b/tests/tests/telecom/Android.mk
@@ -27,8 +27,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
ctstestrunner \
- android-support-test \
- legacy-android-test
+ android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index fb60586..46de0b7 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Telecom Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="telecom" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
<option name="token" value="sim-card" />
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index ea29552..321d0b5 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -100,6 +100,7 @@
@Override
public void onCallStateChanged(int state, String number) {
+ Log.i(TAG, "onCallStateChanged: state=" + state + ", number=%s" + number);
mCallStates.add(Pair.create(state, number));
mCallbackSemaphore.release();
}
@@ -571,7 +572,11 @@
void verifyPhoneStateListenerCallbacksForCall(int expectedCallState) throws Exception {
assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
- Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(0);
+ // Get the most recent callback; it is possible that there was an initial state reported due
+ // to the fact that TelephonyManager will sometimes give an initial state back to the caller
+ // when the listener is registered.
+ Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(
+ mPhoneStateListener.mCallStates.size() - 1);
assertEquals(expectedCallState, (int) callState.first);
assertEquals(getTestNumber().getSchemeSpecificPart(), callState.second);
}
diff --git a/tests/tests/telecom2/Android.mk b/tests/tests/telecom2/Android.mk
index 8e9c8a5d..5b22a92 100644
--- a/tests/tests/telecom2/Android.mk
+++ b/tests/tests/telecom2/Android.mk
@@ -26,8 +26,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
src_dirs := src \
../telecom/src/android/telecom/cts/SelfManagedConnection.java \
diff --git a/tests/tests/telecom2/AndroidTest.xml b/tests/tests/telecom2/AndroidTest.xml
index f4d8e86..d38f24b 100644
--- a/tests/tests/telecom2/AndroidTest.xml
+++ b/tests/tests/telecom2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Telecom2 Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="telecom" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/telecom3/Android.mk b/tests/tests/telecom3/Android.mk
index 86cb859..3138e6c 100644
--- a/tests/tests/telecom3/Android.mk
+++ b/tests/tests/telecom3/Android.mk
@@ -26,6 +26,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
src_dirs := src \
../telecom/src/android/telecom/cts/SelfManagedConnection.java \
../telecom/src/android/telecom/cts/CtsSelfManagedConnectionService.java \
diff --git a/tests/tests/telecom3/AndroidTest.xml b/tests/tests/telecom3/AndroidTest.xml
index 354581a..75b1d0d 100644
--- a/tests/tests/telecom3/AndroidTest.xml
+++ b/tests/tests/telecom3/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Configuration for Telecom3 Tests">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="telecom" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/telephony/Android.mk b/tests/tests/telephony/Android.mk
index 7bd88fe..2e350b0 100644
--- a/tests/tests/telephony/Android.mk
+++ b/tests/tests/telephony/Android.mk
@@ -26,8 +26,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- compatibility-device-util \
- legacy-android-test
+ compatibility-device-util
LOCAL_HOST_SHARED_LIBRARIES := compatibility-device-telephony-preconditions
@@ -45,6 +44,7 @@
# uncomment when b/13250611 is fixed
#LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.base
include $(BUILD_CTS_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/telephony/AndroidTest.xml b/tests/tests/telephony/AndroidTest.xml
index 1e48ebf..a696dfc 100644
--- a/tests/tests/telephony/AndroidTest.xml
+++ b/tests/tests/telephony/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Telephony test cases">
+ <option name="test-suite-tag" value="cts" />
<target_preparer class="android.telephony.cts.preconditions.TelephonyPreparer">
<option name="apk" value="CtsTelephonyPreparerApp.apk" />
<option name="package" value="android.telephony.cts.preconditions.app" />
diff --git a/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk b/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
index cbd18b2..748b6963 100644
--- a/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
+++ b/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
@@ -14,7 +14,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_SDK_VERSION := test_current
-LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_COMPATIBILITY_SUITE := cts vts
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/tests/tests/telephony/preconditions/app/Android.mk b/tests/tests/telephony/preconditions/app/Android.mk
index a5fa396..fb64cd2 100644
--- a/tests/tests/telephony/preconditions/app/Android.mk
+++ b/tests/tests/telephony/preconditions/app/Android.mk
@@ -31,6 +31,8 @@
compatibility-device-util \
compatibility-device-preconditions
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
+
# tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
index 8ffee99..74f9d13 100755
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -283,6 +283,24 @@
}
}
+
+ public void testSmsNotPersisted_failsWithoutCarrierPermissions() throws Exception {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
+ TextUtils.isEmpty(mDestAddr));
+
+ try {
+ getSmsManager().sendTextMessageWithoutPersisting(mDestAddr, null /*scAddress */,
+ mDestAddr, mSentIntent, mDeliveredIntent);
+ fail("We should get a SecurityException due to not having carrier privileges");
+ } catch (SecurityException e) {
+ // Success
+ }
+ }
+
private void init() {
mSendReceiver.reset();
mDeliveryReceiver.reset();
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index 79097e0..d0b219d 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -190,6 +190,7 @@
mTelephonyManager.getPhoneCount();
mTelephonyManager.getDataEnabled();
mTelephonyManager.getNetworkSpecifier();
+ mTelephonyManager.getNai();
TelecomManager telecomManager = (TelecomManager) getContext()
.getSystemService(Context.TELECOM_SERVICE);
PhoneAccountHandle defaultAccount = telecomManager
@@ -358,12 +359,12 @@
}
private void assertSerialNumber() {
- assertNotNull("Non-telephony devices must have a Build.SERIAL number.",
- Build.SERIAL);
+ assertNotNull("Non-telephony devices must have a Build.getSerial() number.",
+ Build.getSerial());
assertTrue("Hardware id must be no longer than 20 characters.",
- Build.SERIAL.length() <= 20);
+ Build.getSerial().length() <= 20);
assertTrue("Hardware id must be alphanumeric.",
- Pattern.matches("[0-9A-Za-z]+", Build.SERIAL));
+ Pattern.matches("[0-9A-Za-z]+", Build.getSerial()));
}
private void assertMacAddress(String macAddress) {
@@ -509,7 +510,7 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
if (!TextUtils.isEmpty(meid)) {
- assertMeidEsn(meid);
+ assertImei(meid);
} else {
// If MEID is empty, then IMEI must not be empty. A phone should have either a
// IMEI or MEID. The validation of IMEI will be checked by testGetImei().
diff --git a/tests/tests/telephony2/Android.mk b/tests/tests/telephony2/Android.mk
index ec72916..ee03500 100644
--- a/tests/tests/telephony2/Android.mk
+++ b/tests/tests/telephony2/Android.mk
@@ -24,8 +24,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- compatibility-device-util \
- legacy-android-test
+ compatibility-device-util
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -35,5 +34,6 @@
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_JAVA_LIBRARIES += android.test.runner
+LOCAL_JAVA_LIBRARIES += android.test.base
include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/telephony2/AndroidTest.xml b/tests/tests/telephony2/AndroidTest.xml
index 78d8d6f..27b9070 100644
--- a/tests/tests/telephony2/AndroidTest.xml
+++ b/tests/tests/telephony2/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Telephony test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="telecom" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/text/Android.mk b/tests/tests/text/Android.mk
index 15a7bcc..7800ee9 100644
--- a/tests/tests/text/Android.mk
+++ b/tests/tests/text/Android.mk
@@ -21,7 +21,7 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES += \
compatibility-device-util \
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index 40a0b50..6a998a8 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -19,6 +19,7 @@
package="android.text.cts">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application android:maxRecents="1">
<uses-library android:name="android.test.runner" />
diff --git a/tests/tests/text/AndroidTest.xml b/tests/tests/text/AndroidTest.xml
index 2b8c733..296fa08 100644
--- a/tests/tests/text/AndroidTest.xml
+++ b/tests/tests/text/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Text test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/text/OWNERS b/tests/tests/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/tests/tests/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/tests/tests/text/assets/ellipsis_test_font.ttf b/tests/tests/text/assets/ellipsis_test_font.ttf
new file mode 100644
index 0000000..ad0d79b
--- /dev/null
+++ b/tests/tests/text/assets/ellipsis_test_font.ttf
Binary files differ
diff --git a/tests/tests/text/assets/ellipsis_test_font.ttx b/tests/tests/text/assets/ellipsis_test_font.ttx
new file mode 100644
index 0000000..0c74b47
--- /dev/null
+++ b/tests/tests/text/assets/ellipsis_test_font.ttx
@@ -0,0 +1,238 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="a"/>
+ <GlyphID id="2" name="ellipsis"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x10000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="0"/>
+ <mtx name="a" width="1000" lsb="0"/>
+ <mtx name="ellipsis" width="1000" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="3" platEncID="10" language="0">
+ <map code="0x0061" name="a" /> <!-- LATIN SMALL LETTER A -->
+ <map code="0x2026" name="ellipsis" /> <!-- HORIZONTAL ELLIPSIS -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="a" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="ellipsis" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2017 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <PairPos index="0" Format="1">
+ <Coverage Format="1">
+ <Glyph value="a"/>
+ <Glyph value="ellipsis"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <PairSet index="0">
+ <PairValueRecord index="0">
+ <SecondGlyph value="ellipsis"/>
+ <Value1 XAdvance="500"/>
+ </PairValueRecord>
+ </PairSet>
+ <PairSet index="1">
+ <PairValueRecord index="0">
+ <SecondGlyph value="a"/>
+ <Value1 XAdvance="500"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+</ttFont>
diff --git a/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttf b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
new file mode 100644
index 0000000..1bad6fe
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttx b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
new file mode 100644
index 0000000..0cf0f79
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="0em"/>
+ <GlyphID id="2" name="1em"/>
+ <GlyphID id="3" name="3em"/>
+ <GlyphID id="4" name="5em"/>
+ <GlyphID id="5" name="7em"/>
+ <GlyphID id="6" name="10em"/>
+ <GlyphID id="7" name="50em"/>
+ <GlyphID id="8" name="100em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="100"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="50" lsb="0"/>
+ <mtx name="0em" width="0" lsb="0"/>
+ <mtx name="1em" width="100" lsb="0"/>
+ <mtx name="3em" width="300" lsb="0"/>
+ <mtx name="5em" width="500" lsb="0"/>
+ <mtx name="7em" width="700" lsb="0"/>
+ <mtx name="10em" width="1000" lsb="0"/>
+ <mtx name="50em" width="5000" lsb="0"/>
+ <mtx name="100em" width="10000" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x0020" name="10em" />
+ <map code="0x002e" name="10em" /> <!-- . -->
+ <map code="0x0043" name="100em" /> <!-- C -->
+ <map code="0x0049" name="1em" /> <!-- I -->
+ <map code="0x004c" name="50em" /> <!-- L -->
+ <map code="0x0056" name="5em" /> <!-- V -->
+ <map code="0x0058" name="10em" /> <!-- X -->
+ <map code="0x005f" name="0em" /> <!-- _ -->
+ <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
+ <map code="0x10331" name="10em" />
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="0em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for StaticLayoutLineBreakingTest
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for StaticLayoutLineBreakingTest
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/tests/text/res/layout/keylistener_layout.xml b/tests/tests/text/res/layout/keylistener_layout.xml
index 84a7a14..e26e815 100644
--- a/tests/tests/text/res/layout/keylistener_layout.xml
+++ b/tests/tests/text/res/layout/keylistener_layout.xml
@@ -19,6 +19,7 @@
android:id="@+id/keylistener_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textSize="10dp"
-/>
+ android:textSize="10dp">
+ <requestFocus />
+</android.text.method.cts.EditTextNoIme>
diff --git a/tests/tests/text/src/android/text/cts/BoringLayoutTest.java b/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
index e78fb3e..f2a7b5a 100644
--- a/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.anyInt;
@@ -36,6 +37,8 @@
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.BoringLayout;
@@ -345,6 +348,91 @@
anyInt(), anyInt(), anyFloat(), anyFloat(), any(Paint.class));
}
+ @Test
+ public void testEllipsize_End() {
+ // When we try to ellipsize "aaaa" into a thinner 3.4 em space, we originally think "aa…"
+ // would fit, but after measuring the new text, we find that it doesn't and we need to
+ // retry.
+ final float size = 100.0f;
+ final int allocatedWidth = (int) (3.4f * size);
+ final BoringLayout layout = new BoringLayout(
+ "aaaa",
+ getTextPaintForEllipsize(size),
+ allocatedWidth,
+ DEFAULT_ALIGN,
+ SPACING_MULT_NO_SCALE,
+ SPACING_ADD_NO_SCALE,
+ createMetrics(0, 0, 0, 0, allocatedWidth, 0),
+ false /* includepad */,
+ TextUtils.TruncateAt.END,
+ allocatedWidth);
+ assertEquals("a\u2026\uFEFF\uFEFF", layout.getText().toString());
+ }
+
+ @Test
+ public void testEllipsize_Start() {
+ // When we try to ellipsize "aaaa" into a thinner 3.4 em space, we originally think "…aa"
+ // would fit, but after measuring the new text, we find that it doesn't and we need to
+ // retry.
+ final float size = 100.0f;
+ final int allocatedWidth = (int) (3.4f * size);
+ final BoringLayout layout = new BoringLayout(
+ "aaaa",
+ getTextPaintForEllipsize(size),
+ allocatedWidth,
+ DEFAULT_ALIGN,
+ SPACING_MULT_NO_SCALE,
+ SPACING_ADD_NO_SCALE,
+ createMetrics(0, 0, 0, 0, allocatedWidth, 0),
+ false /* includepad */,
+ TextUtils.TruncateAt.START,
+ allocatedWidth);
+ assertEquals("\u2026\uFEFF\uFEFFa", layout.getText().toString());
+ }
+
+ @Test
+ public void testEllipsize_Middle() {
+ // When we try to ellipsize "aaaaaa" into a thinner 5.9 em space, we originally think
+ // "aa…aa" would fit, but after measuring the new text, we find that it doesn't and we need
+ // to retry.
+ final float size = 100.0f;
+ final int allocatedWidth = (int) (5.9f * size);
+ final BoringLayout layout = new BoringLayout(
+ "aaaaaa",
+ getTextPaintForEllipsize(size),
+ allocatedWidth,
+ DEFAULT_ALIGN,
+ SPACING_MULT_NO_SCALE,
+ SPACING_ADD_NO_SCALE,
+ createMetrics(0, 0, 0, 0, allocatedWidth, 0),
+ false /* includepad */,
+ TextUtils.TruncateAt.MIDDLE,
+ allocatedWidth);
+ final String ellipsizedString = layout.getText().toString();
+ assertTrue("aa\u2026\uFEFF\uFEFFa".equals(ellipsizedString)
+ || "a\u2026\uFEFF\uFEFFaa".equals(ellipsizedString));
+ }
+
+ private TextPaint getTextPaintForEllipsize(float size) {
+ // The font used in this method has two glyphs defined for "a" and ellipsis. Both are one
+ // em wide. But the glyphs are kerned: whenever the "a" is followed or preceded by an
+ // ellipsis, half an em is added between them as kerning. This means that:
+ // "aaaa" is 4 ems wide,
+ // "aaa…" is 4.5 ems wide,
+ // "aa…" is 3.5 ems wide,
+ // "a…" is 2.5 ems wide,
+ // "aa…aa" is 6 ems wide,
+ // "aa…a" is 5 ems wide,
+ // "a…aa" is 5 ems wide,
+ // "a…a" is 4 ems wide,
+ // "…a" is 2.5 ems wide.
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(Typeface.createFromAsset(
+ InstrumentationRegistry.getTargetContext().getAssets(), "ellipsis_test_font.ttf"));
+ paint.setTextSize(size);
+ return paint;
+ }
+
private static Metrics createMetrics(
final int top,
final int ascent,
diff --git a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
index 02139c5..ec36b0e 100644
--- a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
@@ -17,21 +17,25 @@
package android.text.cts;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
import android.graphics.Paint.FontMetricsInt;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.DynamicLayout;
import android.text.Layout;
+import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
+import android.text.style.TypefaceSpan;
import org.junit.Before;
import org.junit.Test;
@@ -44,6 +48,7 @@
private static final float SPACING_MULT_NO_SCALE = 1.0f;
private static final float SPACING_ADD_NO_SCALE = 0.0f;
private static final int DEFAULT_OUTER_WIDTH = 150;
+ private static final int ELLIPSIZE_WIDTH = 8;
private static final CharSequence SINGLELINE_CHAR_SEQUENCE = "......";
private static final String[] TEXT = {"CharSequence\n", "Char\tSequence\n", "CharSequence"};
private static final CharSequence MULTLINE_CHAR_SEQUENCE = TEXT[0] + TEXT[1] + TEXT[2];
@@ -110,7 +115,7 @@
DEFAULT_ALIGN,
SPACING_MULT_NO_SCALE,
SPACING_ADD_NO_SCALE,
- true,
+ true /* include pad */,
TextUtils.TruncateAt.START,
DEFAULT_OUTER_WIDTH);
assertEquals(0, dynamicLayout.getEllipsisCount(LINE1));
@@ -118,6 +123,24 @@
assertEquals(DEFAULT_OUTER_WIDTH, dynamicLayout.getEllipsizedWidth());
}
+ // This could cause a crash in an older version of ellipsization code.
+ @Test
+ public void testEllipsisWithReflow() {
+ final String text = "Ham & Cheese.sandwich";
+ final int width = 1 << 20;
+ final int ellipsizedWidth = 2 * (int) mDefaultPaint.getTextSize();
+ final DynamicLayout dynamicLayout = new DynamicLayout(text,
+ text,
+ mDefaultPaint,
+ width,
+ DEFAULT_ALIGN,
+ SPACING_MULT_NO_SCALE,
+ SPACING_ADD_NO_SCALE,
+ true /* include pad */,
+ TextUtils.TruncateAt.END,
+ ellipsizedWidth);
+ }
+
/*
* Test whether include the padding to calculate the layout.
* 1. Include padding while calculate the layout.
@@ -194,6 +217,62 @@
assertEquals(TEXT[0].length() + TEXT[1].length(), mDynamicLayout.getLineStart(LINE2));
}
+ @Test
+ public void testLineSpacing() {
+ SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc");
+ final float spacingMultiplier = 2f;
+ final float spacingAdd = 4;
+ final int width = 1000;
+ final TextPaint textPaint = new TextPaint();
+ // create the DynamicLayout
+ final DynamicLayout dynamicLayout = new DynamicLayout(text,
+ textPaint,
+ width,
+ ALIGN_NORMAL,
+ spacingMultiplier,
+ spacingAdd,
+ false /*includepad*/);
+
+ // create a StaticLayout with same text, this will define the expectations
+ Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+ spacingMultiplier);
+ assertLineSpecs(expected, dynamicLayout);
+
+ // add a new line to the end, DynamicLayout will re-calculate
+ text = text.append("\nd");
+ expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+ spacingMultiplier);
+ assertLineSpecs(expected, dynamicLayout);
+
+ // insert a next line and a char as the new second line
+ text = text.insert(TextUtils.indexOf(text, '\n') + 1, "a1\n");
+ expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+ spacingMultiplier);
+ assertLineSpecs(expected, dynamicLayout);
+ }
+
+ @Test
+ public void testLineSpacing_textEndingWithNextLine() {
+ final SpannableStringBuilder text = new SpannableStringBuilder("a\n");
+ final float spacingMultiplier = 2f;
+ final float spacingAdd = 4f;
+ final int width = 1000;
+ final TextPaint textPaint = new TextPaint();
+ // create the DynamicLayout
+ final DynamicLayout dynamicLayout = new DynamicLayout(text,
+ textPaint,
+ width,
+ ALIGN_NORMAL,
+ spacingMultiplier,
+ spacingAdd,
+ false /*includepad*/);
+
+ // create a StaticLayout with same text, this will define the expectations
+ final Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
+ spacingMultiplier);
+ assertLineSpecs(expected, dynamicLayout);
+ }
+
private Layout createStaticLayout(CharSequence text, TextPaint textPaint, int width,
float spacingAdd, float spacingMultiplier) {
return StaticLayout.Builder.obtain(text, 0,
@@ -253,4 +332,110 @@
spacingMultiplier);
assertLineSpecs(expected, dynamicLayout);
}
+
+ @Test
+ public void testBuilder_obtain() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ final DynamicLayout layout = builder.build();
+ // Check values passed to obtain().
+ assertEquals(MULTLINE_CHAR_SEQUENCE, layout.getText());
+ assertEquals(mDefaultPaint, layout.getPaint());
+ assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
+ // Check default values.
+ assertEquals(Layout.Alignment.ALIGN_NORMAL, layout.getAlignment());
+ assertEquals(0.0f, layout.getSpacingAdd(), 0.0f);
+ assertEquals(1.0f, layout.getSpacingMultiplier(), 0.0f);
+ assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testBuilder_obtainWithNullText() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(null, mDefaultPaint, 0);
+ final DynamicLayout layout = builder.build();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testBuilder_obtainWithNullPaint() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ null, 0);
+ final DynamicLayout layout = builder.build();
+ }
+
+ @Test
+ public void testBuilder_setDisplayTest() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setDisplayText(SINGLELINE_CHAR_SEQUENCE);
+ final DynamicLayout layout = builder.build();
+ assertEquals(SINGLELINE_CHAR_SEQUENCE, layout.getText());
+ }
+
+ @Test
+ public void testBuilder_setAlignment() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setAlignment(DEFAULT_ALIGN);
+ final DynamicLayout layout = builder.build();
+ assertEquals(DEFAULT_ALIGN, layout.getAlignment());
+ }
+
+ @Test
+ public void testBuilder_setLineSpacing() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setLineSpacing(1.0f, 2.0f);
+ final DynamicLayout layout = builder.build();
+ assertEquals(1.0f, layout.getSpacingAdd(), 0.0f);
+ assertEquals(2.0f, layout.getSpacingMultiplier(), 0.0f);
+ }
+
+ @Test
+ public void testBuilder_ellipsization() {
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setEllipsize(TextUtils.TruncateAt.END)
+ .setEllipsizedWidth(ELLIPSIZE_WIDTH);
+ final DynamicLayout layout = builder.build();
+ assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth());
+ assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
+ for (int i = 0; i < TEXT.length; i++) {
+ if (i == TEXT.length - 1) { // last line
+ assertTrue(layout.getEllipsisCount(i) > 0);
+ } else {
+ assertEquals(0, layout.getEllipsisCount(i));
+ }
+ }
+ }
+
+ @Test
+ public void testBuilder_otherSetters() {
+ // Setter methods that cannot be directly tested.
+ // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setJustificationMode.
+ final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
+ .setIncludePad(true)
+ .setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
+ final DynamicLayout layout = builder.build();
+ assertNotNull(layout);
+ }
+
+ @Test
+ public void testReflow_afterSpanChangedShouldNotThrowException() {
+ final SpannableStringBuilder builder = new SpannableStringBuilder("crash crash crash!!");
+
+ final TypefaceSpan span = mock(TypefaceSpan.class);
+ builder.setSpan(span, 1, 4, SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ final DynamicLayout layout = DynamicLayout.Builder.obtain(builder,
+ new TextPaint(), Integer.MAX_VALUE).build();
+ try {
+ builder.insert(1, "Hello there\n\n");
+ } catch (Throwable e) {
+ throw new RuntimeException("Inserting text into DynamicLayout should not crash", e);
+ }
+ }
+
}
diff --git a/tests/tests/text/src/android/text/cts/PremeasuredTextTest.java b/tests/tests/text/src/android/text/cts/PremeasuredTextTest.java
new file mode 100644
index 0000000..3d3ecd0
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/PremeasuredTextTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.PremeasuredText;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextDirectionHeuristic;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.LocaleSpan;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PremeasuredTextTest {
+
+ private static final CharSequence NULL_CHAR_SEQUENCE = null;
+ private static final String STRING = "Hello, World!";
+ private static final String MULTIPARA_STRING = "Hello,\nWorld!";
+
+ private static final int SPAN_START = 3;
+ private static final int SPAN_END = 7;
+ private static final LocaleSpan SPAN = new LocaleSpan(Locale.US);
+ private static final Spanned SPANNED;
+ static {
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(STRING);
+ ssb.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ SPANNED = ssb;
+ }
+
+ private static final TextPaint PAINT = new TextPaint();
+
+ private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR;
+
+ @Test
+ public void testBuild() {
+ assertNotNull(PremeasuredText.build(STRING, PAINT, LTR));
+ assertNotNull(PremeasuredText.build(STRING, PAINT, LTR, 0, STRING.length()));
+ assertNotNull(PremeasuredText.build(STRING, PAINT, LTR, 1, STRING.length() - 1));
+
+ assertNotNull(PremeasuredText.build(SPANNED, PAINT, LTR));
+ assertNotNull(PremeasuredText.build(SPANNED, PAINT, LTR, 0, STRING.length()));
+ assertNotNull(PremeasuredText.build(SPANNED, PAINT, LTR, 1, STRING.length() - 1));
+ }
+
+ @Test
+ public void testBuild_withNull() {
+ try {
+ PremeasuredText.build(NULL_CHAR_SEQUENCE, PAINT, LTR);
+ fail();
+ } catch (NullPointerException e) {
+ // pass
+ }
+ try {
+ PremeasuredText.build(NULL_CHAR_SEQUENCE, PAINT, LTR, 0, 0);
+ fail();
+ } catch (NullPointerException e) {
+ // pass
+ }
+
+ try {
+ PremeasuredText.build(STRING, null, LTR);
+ fail();
+ } catch (NullPointerException e) {
+ // pass
+ }
+ try {
+ PremeasuredText.build(STRING, null, LTR, 0, 1);
+ fail();
+ } catch (NullPointerException e) {
+ // pass
+ }
+
+ try {
+ PremeasuredText.build(STRING, PAINT, null);
+ fail();
+ } catch (NullPointerException e) {
+ // pass
+ }
+ try {
+ PremeasuredText.build(STRING, PAINT, null, 0, 1);
+ fail();
+ } catch (NullPointerException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testBuild_withInvalidRange() {
+ try {
+ PremeasuredText.build(STRING, PAINT, LTR, -1, -1);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+ try {
+ PremeasuredText.build(STRING, PAINT, LTR, 100000, 100000);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testCharSequenceInteferface() {
+ final CharSequence s = PremeasuredText.build(STRING, PAINT, LTR);
+ assertEquals(STRING.length(), s.length());
+ assertEquals('H', s.charAt(0));
+ assertEquals('e', s.charAt(1));
+ assertEquals('l', s.charAt(2));
+ assertEquals('l', s.charAt(3));
+ assertEquals('o', s.charAt(4));
+ assertEquals(',', s.charAt(5));
+ assertEquals("Hello, World!", s.toString());
+
+ // Even measure the part of the text, the CharSequence interface still works for original
+ // text.
+ // TODO: Should this work like substring?
+ final CharSequence s2 = PremeasuredText.build(STRING, PAINT, LTR, 7, STRING.length());
+ assertEquals(STRING.length(), s2.length());
+ assertEquals('H', s2.charAt(0));
+ assertEquals('e', s2.charAt(1));
+ assertEquals('l', s2.charAt(2));
+ assertEquals('l', s2.charAt(3));
+ assertEquals('o', s2.charAt(4));
+ assertEquals(',', s2.charAt(5));
+ assertEquals("Hello, World!", s2.toString());
+
+ final CharSequence s3 = s.subSequence(0, 3);
+ assertEquals(3, s3.length());
+ assertEquals('H', s3.charAt(0));
+ assertEquals('e', s3.charAt(1));
+ assertEquals('l', s3.charAt(2));
+
+ }
+
+ @Test
+ public void testSpannedInterface_Spanned() {
+ final Spanned s = PremeasuredText.build(SPANNED, PAINT, LTR);
+ final LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class);
+ assertNotNull(spans);
+ assertEquals(1, spans.length);
+ assertEquals(SPAN, spans[0]);
+
+ assertEquals(SPAN_START, s.getSpanStart(SPAN));
+ assertEquals(SPAN_END, s.getSpanEnd(SPAN));
+ assertTrue((s.getSpanFlags(SPAN) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0);
+
+ assertEquals(SPAN_START, s.nextSpanTransition(0, s.length(), LocaleSpan.class));
+ assertEquals(SPAN_END, s.nextSpanTransition(SPAN_START, s.length(), LocaleSpan.class));
+
+ final Spanned s2 = PremeasuredText.build(SPANNED, PAINT, LTR, 7, SPANNED.length());
+ final LocaleSpan[] spans2 = s2.getSpans(0, s2.length(), LocaleSpan.class);
+ assertNotNull(spans2);
+ assertEquals(1, spans2.length);
+ assertEquals(SPAN, spans2[0]);
+
+ assertEquals(SPAN_START, s2.getSpanStart(SPAN));
+ assertEquals(SPAN_END, s2.getSpanEnd(SPAN));
+ assertTrue((s2.getSpanFlags(SPAN) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0);
+
+ assertEquals(SPAN_START, s2.nextSpanTransition(0, s2.length(), LocaleSpan.class));
+ assertEquals(SPAN_END, s2.nextSpanTransition(SPAN_START, s2.length(), LocaleSpan.class));
+ }
+
+ @Test
+ public void testSpannedInterface_String() {
+ final Spanned s = PremeasuredText.build(STRING, PAINT, LTR);
+ LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class);
+ assertNotNull(spans);
+ assertEquals(0, spans.length);
+
+ assertEquals(-1, s.getSpanStart(SPAN));
+ assertEquals(-1, s.getSpanEnd(SPAN));
+ assertEquals(0, s.getSpanFlags(SPAN));
+
+ assertEquals(s.length(), s.nextSpanTransition(0, s.length(), LocaleSpan.class));
+ }
+
+ @Test
+ public void testGetText() {
+ assertSame(STRING, PremeasuredText.build(STRING, PAINT, LTR).getText());
+ assertSame(SPANNED, PremeasuredText.build(SPANNED, PAINT, LTR).getText());
+
+ assertSame(STRING, PremeasuredText.build(STRING, PAINT, LTR, 1, 5).getText());
+ assertSame(SPANNED, PremeasuredText.build(SPANNED, PAINT, LTR, 1, 5).getText());
+ }
+
+ @Test
+ public void testGetStartEnd() {
+ assertEquals(0, PremeasuredText.build(STRING, PAINT, LTR).getStart());
+ assertEquals(STRING.length(), PremeasuredText.build(STRING, PAINT, LTR).getEnd());
+
+ assertEquals(1, PremeasuredText.build(STRING, PAINT, LTR, 1, 5).getStart());
+ assertEquals(5, PremeasuredText.build(STRING, PAINT, LTR, 1, 5).getEnd());
+
+ assertEquals(0, PremeasuredText.build(SPANNED, PAINT, LTR).getStart());
+ assertEquals(SPANNED.length(), PremeasuredText.build(SPANNED, PAINT, LTR).getEnd());
+
+ assertEquals(1, PremeasuredText.build(SPANNED, PAINT, LTR, 1, 5).getStart());
+ assertEquals(5, PremeasuredText.build(SPANNED, PAINT, LTR, 1, 5).getEnd());
+ }
+
+ @Test
+ public void testGetTextDir() {
+ assertSame(LTR, PremeasuredText.build(STRING, PAINT, LTR).getTextDir());
+ assertSame(LTR, PremeasuredText.build(SPANNED, PAINT, LTR).getTextDir());
+ }
+
+ @Test
+ public void testGetPaint() {
+ // No Paint equality functions. Check only not null.
+ assertNotNull(PremeasuredText.build(STRING, PAINT, LTR).getPaint());
+ assertNotNull(PremeasuredText.build(SPANNED, PAINT, LTR).getPaint());
+ }
+
+ @Test
+ public void testGetParagraphCount() {
+ final PremeasuredText pm = PremeasuredText.build(STRING, PAINT, LTR);
+ assertEquals(1, pm.getParagraphCount());
+ assertEquals(0, pm.getParagraphStart(0));
+ assertEquals(STRING.length(), pm.getParagraphEnd(0));
+
+ final PremeasuredText pm2 = PremeasuredText.build(STRING, PAINT, LTR, 1, 9);
+ assertEquals(1, pm2.getParagraphCount());
+ assertEquals(1, pm2.getParagraphStart(0));
+ assertEquals(9, pm2.getParagraphEnd(0));
+
+ final PremeasuredText pm3 = PremeasuredText.build(MULTIPARA_STRING, PAINT, LTR);
+ assertEquals(2, pm3.getParagraphCount());
+ assertEquals(0, pm3.getParagraphStart(0));
+ assertEquals(7, pm3.getParagraphEnd(0));
+ assertEquals(7, pm3.getParagraphStart(1));
+ assertEquals(pm3.length(), pm3.getParagraphEnd(1));
+
+ final PremeasuredText pm4 = PremeasuredText.build(MULTIPARA_STRING, PAINT, LTR, 1, 5);
+ assertEquals(1, pm4.getParagraphCount());
+ assertEquals(1, pm4.getParagraphStart(0));
+ assertEquals(5, pm4.getParagraphEnd(0));
+
+ final PremeasuredText pm5 = PremeasuredText.build(MULTIPARA_STRING, PAINT, LTR, 1, 9);
+ assertEquals(2, pm5.getParagraphCount());
+ assertEquals(1, pm5.getParagraphStart(0));
+ assertEquals(7, pm5.getParagraphEnd(0));
+ assertEquals(7, pm5.getParagraphStart(1));
+ assertEquals(9, pm5.getParagraphEnd(1));
+ }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/SelectionTest.java b/tests/tests/text/src/android/text/cts/SelectionTest.java
index f920bda..2aac83b 100644
--- a/tests/tests/text/src/android/text/cts/SelectionTest.java
+++ b/tests/tests/text/src/android/text/cts/SelectionTest.java
@@ -23,6 +23,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
@@ -275,6 +276,56 @@
}
@Test
+ public void testMoveUpAfterTyping() {
+ CharSequence text = "aaa\nmm";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 1, 1);
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(5, Selection.getSelectionStart(builder));
+ assertEquals(5, Selection.getSelectionEnd(builder));
+
+ builder.insert(5, "mm");
+ layout = new StaticLayout(builder, new TextPaint(), 200, Layout.Alignment.ALIGN_NORMAL,
+ 0, 0, false);
+ assertEquals(7, Selection.getSelectionStart(builder));
+ assertEquals(7, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testMoveUpKeepsOriginalMemoryPosition() {
+ CharSequence text = "aa\nm";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 1, 1);
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(4, Selection.getSelectionStart(builder));
+ assertEquals(4, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+ }
+
+ @Test
public void testMoveDown() {
CharSequence text = "hello,world\nGoogle";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -313,6 +364,340 @@
}
@Test
+ public void testMoveDownAfterTyping() {
+ CharSequence text = "mm\naaa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 4, 4);
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+
+ builder.insert(1, "mm");
+ layout = new StaticLayout(builder, new TextPaint(), 200, Layout.Alignment.ALIGN_NORMAL,
+ 0, 0, false);
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(8, Selection.getSelectionStart(builder));
+ assertEquals(8, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testMoveDownKeepsOriginalMemoryPosition() {
+ CharSequence text = "m\naa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 3, 3);
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+ }
+
+ @Test
+ public void testMemoryPositionResetByHorizontalMovement() {
+ CharSequence text = "m\naa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 3, 3);
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveRight(builder, layout));
+ assertEquals(4, Selection.getSelectionStart(builder));
+ assertEquals(4, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(4, Selection.getSelectionStart(builder));
+ assertEquals(4, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveLeft(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 3, 3);
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+ }
+
+ @Test
+ public void testMemoryPositionResetByRemoveSelection() {
+ CharSequence text = "m\naa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 3, 3);
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.removeSelection(builder);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+ }
+
+ @Test
+ public void testMultilineLengthMoveDown() {
+ CharSequence text = "a\n\na";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 1);
+ // Move down to empty line
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(2, Selection.getSelectionStart(builder));
+ assertEquals(2, Selection.getSelectionEnd(builder));
+
+ // Move down to third line
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(4, Selection.getSelectionStart(builder));
+ assertEquals(4, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testMultilineLengthExtendDown() {
+ CharSequence text = "Google\n\nhello, world";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 1, 3);
+ assertTrue(Selection.extendDown(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(7, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.extendDown(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(15, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.extendDown(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(text.length(), Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testExtendDownKeepsOriginalMemoryPosition() {
+ CharSequence text = "m\naa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 3, 3);
+ assertTrue(Selection.extendUp(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.extendDown(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+ }
+
+ @Test
+ public void testMultilineLengthMoveUp() {
+ CharSequence text = "a\n\na";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 4);
+ // Move up to empty line
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(2, Selection.getSelectionStart(builder));
+ assertEquals(2, Selection.getSelectionEnd(builder));
+
+ // Move up to first line
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testMultilineLengthExtendUp() {
+ CharSequence text = "Google\n\nhello, world";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.extendUp(builder, layout));
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(0, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 9, 16);
+ assertTrue(Selection.extendUp(builder, layout));
+ assertEquals(9, Selection.getSelectionStart(builder));
+ assertEquals(7, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.extendUp(builder, layout));
+ assertEquals(9, Selection.getSelectionStart(builder));
+ assertEquals(4, Selection.getSelectionEnd(builder));
+
+ assertTrue(Selection.extendUp(builder, layout));
+ assertEquals(9, Selection.getSelectionStart(builder));
+ assertEquals(0, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testExtendUpKeepsOriginalMemoryPosition() {
+ CharSequence text = "aa\nm";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(builder, new TextPaint(), 200,
+ Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+ assertEquals(0,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ Selection.setSelection(builder, 1, 1);
+ assertTrue(Selection.extendDown(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(4, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+
+ assertTrue(Selection.extendUp(builder, layout));
+ assertEquals(1, Selection.getSelectionStart(builder));
+ assertEquals(1, Selection.getSelectionEnd(builder));
+ assertEquals(1,
+ builder.getSpans(0, builder.length(), Selection.MemoryTextWatcher.class).length);
+ }
+
+ @Test
+ public void testMultilineLengthMoveDownAfterSelection() {
+ CharSequence text = "aaaaa\n\naaaaa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 3);
+ // Move down to empty line
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(6, Selection.getSelectionStart(builder));
+ assertEquals(6, Selection.getSelectionEnd(builder));
+
+ // Move down to third line
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(3, Selection.getSelectionStart(builder));
+ assertEquals(3, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 5);
+ // Move down to empty line
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(6, Selection.getSelectionStart(builder));
+ assertEquals(6, Selection.getSelectionEnd(builder));
+
+ // Move down to third line
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(5, Selection.getSelectionStart(builder));
+ assertEquals(5, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
+ public void testMultilineLengthMoveUpAfterSelection() {
+ CharSequence text = "aaaaa\n\naaaaa";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ StaticLayout layout = new StaticLayout(text, new TextPaint(), 200, null, 0, 0, false);
+ assertEquals(-1, Selection.getSelectionStart(builder));
+ assertEquals(-1, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 10);
+ // Move up to empty line
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(6, Selection.getSelectionStart(builder));
+ assertEquals(6, Selection.getSelectionEnd(builder));
+
+ // Move down to third line
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(10, Selection.getSelectionStart(builder));
+ assertEquals(10, Selection.getSelectionEnd(builder));
+
+ Selection.setSelection(builder, 12);
+ // Move up to empty line
+ assertTrue(Selection.moveUp(builder, layout));
+ assertEquals(6, Selection.getSelectionStart(builder));
+ assertEquals(6, Selection.getSelectionEnd(builder));
+
+ // Move down to third line
+ assertTrue(Selection.moveDown(builder, layout));
+ assertEquals(12, Selection.getSelectionStart(builder));
+ assertEquals(12, Selection.getSelectionEnd(builder));
+ }
+
+ @Test
public void testExtendSelection() {
CharSequence text = "hello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
new file mode 100644
index 0000000..adb8f97
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingTest.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2012 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.text.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;;
+import android.graphics.Typeface;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.StaticLayout;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.MetricAffectingSpan;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutLineBreakingTest {
+ // Span test are currently not supported because text measurement uses the MeasuredText
+ // internal mWorkPaint instead of the provided MockTestPaint.
+ private static final boolean SPAN_TESTS_SUPPORTED = false;
+ private static final boolean DEBUG = false;
+
+ private static final float SPACE_MULTI = 1.0f;
+ private static final float SPACE_ADD = 0.0f;
+ private static final int WIDTH = 100;
+ private static final Alignment ALIGN = Alignment.ALIGN_NORMAL;
+
+ private static final char SURR_FIRST = '\uD800';
+ private static final char SURR_SECOND = '\uDF31';
+
+ private static final int[] NO_BREAK = new int[] {};
+
+ private static final TextPaint sTextPaint = new TextPaint();
+
+ static {
+ // The test font has following coverage and width.
+ // U+0020: 10em
+ // U+002E (.): 10em
+ // U+0043 (C): 100em
+ // U+0049 (I): 1em
+ // U+004C (L): 50em
+ // U+0056 (V): 5em
+ // U+0058 (X): 10em
+ // U+005F (_): 0em
+ // U+FFFD (invalid surrogate will be replaced to this): 7em
+ // U+10331 (\uD800\uDF31): 10em
+ Context context = InstrumentationRegistry.getTargetContext();
+ sTextPaint.setTypeface(Typeface.createFromAsset(context.getAssets(),
+ "fonts/StaticLayoutLineBreakingTestFont.ttf"));
+ sTextPaint.setTextSize(1.0f); // Make 1em == 1px.
+ }
+
+ private static StaticLayout getStaticLayout(CharSequence source, int width,
+ int breakStrategy) {
+ return StaticLayout.Builder.obtain(source, 0, source.length(), sTextPaint, width)
+ .setAlignment(ALIGN)
+ .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+ .setIncludePad(false)
+ .setBreakStrategy(breakStrategy)
+ .build();
+ }
+
+ private static int[] getBreaks(CharSequence source) {
+ return getBreaks(source, WIDTH, Layout.BREAK_STRATEGY_SIMPLE);
+ }
+
+ private static int[] getBreaks(CharSequence source, int width, int breakStrategy) {
+ final StaticLayout staticLayout = getStaticLayout(source, width, breakStrategy);
+
+ final int[] breaks = new int[staticLayout.getLineCount() - 1];
+ for (int line = 0; line < breaks.length; line++) {
+ breaks[line] = staticLayout.getLineEnd(line);
+ }
+ return breaks;
+ }
+
+ private static void debugLayout(CharSequence source, StaticLayout staticLayout) {
+ if (DEBUG) {
+ int count = staticLayout.getLineCount();
+ Log.i("SLLBTest", "\"" + source.toString() + "\": "
+ + count + " lines");
+ for (int line = 0; line < count; line++) {
+ int lineStart = staticLayout.getLineStart(line);
+ int lineEnd = staticLayout.getLineEnd(line);
+ Log.i("SLLBTest", "Line " + line + " [" + lineStart + ".."
+ + lineEnd + "]\t" + source.subSequence(lineStart, lineEnd));
+ }
+ }
+ }
+
+ private static void layout(CharSequence source, int[] breaks) {
+ layout(source, breaks, WIDTH);
+ }
+
+ private static void layout(CharSequence source, int[] breaks, int width) {
+ final int[] breakStrategies = {Layout.BREAK_STRATEGY_SIMPLE,
+ Layout.BREAK_STRATEGY_HIGH_QUALITY};
+ for (int breakStrategy : breakStrategies) {
+ final StaticLayout staticLayout = getStaticLayout(source, width, breakStrategy);
+
+ debugLayout(source, staticLayout);
+
+ final int lineCount = breaks.length + 1;
+ assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
+
+ for (int line = 0; line < lineCount; line++) {
+ final int lineStart = staticLayout.getLineStart(line);
+ final int lineEnd = staticLayout.getLineEnd(line);
+
+ if (line == 0) {
+ assertEquals("Line start for first line", 0, lineStart);
+ } else {
+ assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+ }
+
+ if (line == lineCount - 1) {
+ assertEquals("Line end for last line", source.length(), lineEnd);
+ } else {
+ assertEquals("Line end for line " + line, breaks[line], lineEnd);
+ }
+ }
+ }
+ }
+
+ private static void layoutMaxLines(CharSequence source, int[] breaks, int maxLines) {
+ final StaticLayout staticLayout = StaticLayout.Builder
+ .obtain(source, 0, source.length(), sTextPaint, WIDTH)
+ .setAlignment(ALIGN)
+ .setTextDirection(TextDirectionHeuristics.LTR)
+ .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+ .setIncludePad(false)
+ .setMaxLines(maxLines)
+ .build();
+
+ debugLayout(source, staticLayout);
+
+ final int lineCount = staticLayout.getLineCount();
+
+ for (int line = 0; line < lineCount; line++) {
+ int lineStart = staticLayout.getLineStart(line);
+ int lineEnd = staticLayout.getLineEnd(line);
+
+ if (line == 0) {
+ assertEquals("Line start for first line", 0, lineStart);
+ } else {
+ assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+ }
+
+ if (line == lineCount - 1 && line != breaks.length - 1) {
+ assertEquals("Line end for last line", source.length(), lineEnd);
+ } else {
+ assertEquals("Line end for line " + line, breaks[line], lineEnd);
+ }
+ }
+ }
+
+ private static final int MAX_SPAN_COUNT = 10;
+ private static final int[] sSpanStarts = new int[MAX_SPAN_COUNT];
+ private static final int[] sSpanEnds = new int[MAX_SPAN_COUNT];
+
+ private static MetricAffectingSpan getMetricAffectingSpan() {
+ return new MetricAffectingSpan() {
+ @Override
+ public void updateDrawState(TextPaint tp) { /* empty */ }
+
+ @Override
+ public void updateMeasureState(TextPaint p) { /* empty */ }
+ };
+ }
+
+ /**
+ * Replaces the "<...>" blocks by spans, assuming non overlapping, correctly defined spans
+ * @param text
+ * @return A CharSequence with '<' '>' replaced by MetricAffectingSpan
+ */
+ private static CharSequence spanify(String text) {
+ int startIndex = text.indexOf('<');
+ if (startIndex < 0) return text;
+
+ int spanCount = 0;
+ do {
+ int endIndex = text.indexOf('>');
+ if (endIndex < 0) throw new IllegalArgumentException("Unbalanced span markers");
+
+ text = text.substring(0, startIndex) + text.substring(startIndex + 1, endIndex)
+ + text.substring(endIndex + 1);
+
+ sSpanStarts[spanCount] = startIndex;
+ sSpanEnds[spanCount] = endIndex - 2;
+ spanCount++;
+
+ startIndex = text.indexOf('<');
+ } while (startIndex >= 0);
+
+ SpannableStringBuilder result = new SpannableStringBuilder(text);
+ for (int i = 0; i < spanCount; i++) {
+ result.setSpan(getMetricAffectingSpan(), sSpanStarts[i], sSpanEnds[i],
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ return result;
+ }
+
+ @Test
+ public void testNoLineBreak() {
+ // Width lower than WIDTH
+ layout("", NO_BREAK);
+ layout("I", NO_BREAK);
+ layout("V", NO_BREAK);
+ layout("X", NO_BREAK);
+ layout("L", NO_BREAK);
+ layout("I VILI", NO_BREAK);
+ layout("XXXX", NO_BREAK);
+ layout("LXXXX", NO_BREAK);
+
+ // Width equal to WIDTH
+ layout("C", NO_BREAK);
+ layout("LL", NO_BREAK);
+ layout("L XXXX", NO_BREAK);
+ layout("XXXXXXXXXX", NO_BREAK);
+ layout("XXX XXXXXX", NO_BREAK);
+ layout("XXX XXXX X", NO_BREAK);
+ layout("XXX XXXXX ", NO_BREAK);
+ layout(" XXXXXXXX ", NO_BREAK);
+ layout(" XX XXX ", NO_BREAK);
+ // 0123456789
+
+ // Width greater than WIDTH, but no break
+ layout(" XX XXX ", NO_BREAK);
+ layout("XX XXX XXX ", NO_BREAK);
+ layout("XX XXX XXX ", NO_BREAK);
+ layout("XXXXXXXXXX ", NO_BREAK);
+ // 01234567890
+ }
+
+ @Test
+ public void testOneLineBreak() {
+ // 01234567890
+ layout("XX XXX XXXX", new int[] {7});
+ layout("XX XXXX XXX", new int[] {8});
+ layout("XX XXXXX XX", new int[] {9});
+ layout("XX XXXXXX X", new int[] {10});
+ // 01234567890
+ layout("XXXXXXXXXXX", new int[] {10});
+ layout("XXXXXXXXX X", new int[] {10});
+ layout("XXXXXXXX XX", new int[] {9});
+ layout("XXXXXXX XXX", new int[] {8});
+ layout("XXXXXX XXXX", new int[] {7});
+ // 01234567890
+ layout("LL LL", new int[] {3});
+ layout("LLLL", new int[] {2});
+ layout("C C", new int[] {2});
+ layout("CC", new int[] {1});
+ }
+
+ @Test
+ public void testSpaceAtBreak() {
+ // 0123456789012
+ layout("XXXX XXXXX X", new int[] {11});
+ layout("XXXXXXXXXX X", new int[] {11});
+ layout("XXXXXXXXXV X", new int[] {11});
+ layout("C X", new int[] {2});
+ }
+
+ @Test
+ public void testMultipleSpacesAtBreak() {
+ // 0123456789012
+ layout("LXX XXXX", new int[] {4});
+ layout("LXX XXXX", new int[] {5});
+ layout("LXX XXXX", new int[] {6});
+ layout("LXX XXXX", new int[] {7});
+ layout("LXX XXXX", new int[] {8});
+ }
+
+ @Test
+ public void testZeroWidthCharacters() {
+ // 0123456789012345678901234
+ layout("X_X_X_X_X_X_X_X_X_X", NO_BREAK);
+ layout("___X_X_X_X_X_X_X_X_X_X___", NO_BREAK);
+ layout("C_X", new int[] {2});
+ layout("C__X", new int[] {3});
+ }
+
+ /**
+ * Note that when the text has spans, StaticLayout does not use the provided TextPaint to
+ * measure text runs anymore. This is probably a bug.
+ * To be able to use the fake sTextPaint and make this test pass, use mPaint instead of
+ * mWorkPaint in MeasuredText#addStyleRun
+ */
+ @Test
+ public void testWithSpans() {
+ if (!SPAN_TESTS_SUPPORTED) return;
+
+ layout(spanify("<012 456 89>"), NO_BREAK);
+ layout(spanify("012 <456> 89"), NO_BREAK);
+ layout(spanify("<012> <456>< 89>"), NO_BREAK);
+ layout(spanify("<012> <456> <89>"), NO_BREAK);
+
+ layout(spanify("<012> <456> <89>012"), new int[] {8});
+ layout(spanify("<012> <456> 89<012>"), new int[] {8});
+ layout(spanify("<012> <456> <89><012>"), new int[] {8});
+ layout(spanify("<012> <456> 89 <123>"), new int[] {11});
+ layout(spanify("<012> <456> 89< 123>"), new int[] {11});
+ layout(spanify("<012> <456> <89> <123>"), new int[] {11});
+ layout(spanify("012 456 89 <LXX> XX XX"), new int[] {11, 18});
+ }
+
+ /*
+ * Adding a span to the string should not change the layout, since the metrics are unchanged.
+ */
+ @Test
+ public void testWithOneSpan() {
+ if (!SPAN_TESTS_SUPPORTED) return;
+
+ String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
+ "012 456 89012 456 89012", "0123456789012" };
+
+ MetricAffectingSpan metricAffectingSpan = getMetricAffectingSpan();
+
+ for (String text : texts) {
+ // Get the line breaks without any span
+ int[] breaks = getBreaks(text);
+
+ // Add spans on all possible offsets
+ for (int spanStart = 0; spanStart < text.length(); spanStart++) {
+ for (int spanEnd = spanStart; spanEnd < text.length(); spanEnd++) {
+ SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+ ssb.setSpan(metricAffectingSpan, spanStart, spanEnd,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ layout(ssb, breaks);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testWithTwoSpans() {
+ if (!SPAN_TESTS_SUPPORTED) return;
+
+ String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
+ "012 456 89012 456 89012", "0123456789012" };
+
+ MetricAffectingSpan metricAffectingSpan1 = getMetricAffectingSpan();
+ MetricAffectingSpan metricAffectingSpan2 = getMetricAffectingSpan();
+
+ for (String text : texts) {
+ // Get the line breaks without any span
+ int[] breaks = getBreaks(text);
+
+ // Add spans on all possible offsets
+ for (int spanStart1 = 0; spanStart1 < text.length(); spanStart1++) {
+ for (int spanEnd1 = spanStart1; spanEnd1 < text.length(); spanEnd1++) {
+ SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+ ssb.setSpan(metricAffectingSpan1, spanStart1, spanEnd1,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ for (int spanStart2 = 0; spanStart2 < text.length(); spanStart2++) {
+ for (int spanEnd2 = spanStart2; spanEnd2 < text.length(); spanEnd2++) {
+ ssb.setSpan(metricAffectingSpan2, spanStart2, spanEnd2,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ layout(ssb, breaks);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static String replace(String string, char c, char r) {
+ return string.replaceAll(String.valueOf(c), String.valueOf(r));
+ }
+
+ @Test
+ public void testWithSurrogate() {
+ layout("LX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
+ layout("LXXXX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
+ // LXXXXI (91) + SURR_FIRST + SURR_SECOND (10). Do not break in the middle point of
+ // surrogatge pair.
+ layout("LXXXXI" + SURR_FIRST + SURR_SECOND, new int[] {6});
+
+ // LXXXXI (91) + SURR_SECOND (replaced with REPLACEMENT CHARACTER. width is 7px) fits.
+ // Break just after invalid trailing surrogate.
+ layout("LXXXXI" + SURR_SECOND + SURR_FIRST, new int[] {7});
+
+ layout("C" + SURR_FIRST + SURR_SECOND, new int[] {1});
+ }
+
+ @Test
+ public void testNarrowWidth() {
+ int[] widths = new int[] { 0, 4, 10 };
+ String[] texts = new String[] { "", "X", " ", "XX", " X", "XXX" };
+
+ for (String text: texts) {
+ // 15 is such that only one character will fit
+ int[] breaks = getBreaks(text, 15, Layout.BREAK_STRATEGY_SIMPLE);
+
+ // Width under 15 should all lead to the same line break
+ for (int width: widths) {
+ layout(text, breaks, width);
+ }
+ }
+ }
+
+ @Test
+ public void testNarrowWidthZeroWidth() {
+ int[] widths = new int[] { 1, 4 };
+ for (int width: widths) {
+ layout("X.", new int[] {1}, width);
+ layout("X__", NO_BREAK, width);
+ layout("X__X", new int[] {3}, width);
+ layout("X__X_", new int[] {3}, width);
+
+ layout("_", NO_BREAK, width);
+ layout("__", NO_BREAK, width);
+
+ // TODO: The line breaking algorithms break the line too frequently in the presence of
+ // zero-width characters. The following cases document how line-breaking should behave
+ // in some cases, where the current implementation does not seem reasonable. (Breaking
+ // between a zero-width character that start the line and a character with positive
+ // width does not make sense.) Line-breaking should be fixed so that all the following
+ // tests end up on one line, with no breaks.
+ // layout("_X", NO_BREAK, width);
+ // layout("_X_", NO_BREAK, width);
+ // layout("__X__", NO_BREAK, width);
+ }
+ }
+
+ @Test
+ public void testMaxLines() {
+ layoutMaxLines("C", NO_BREAK, 1);
+ layoutMaxLines("C C", new int[] {2}, 1);
+ layoutMaxLines("C C", new int[] {2}, 2);
+ layoutMaxLines("CC", new int[] {1}, 1);
+ layoutMaxLines("CC", new int[] {1}, 2);
+ }
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index 4f022a5..5029dcf 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -22,22 +22,30 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Typeface;
+import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
import android.text.Editable;
import android.text.Layout;
import android.text.Layout.Alignment;
+import android.text.PremeasuredText;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
+import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.text.method.cts.EditorState;
import android.text.style.StyleSpan;
+import android.text.style.TextAppearanceSpan;
import org.junit.Before;
import org.junit.Test;
@@ -59,6 +67,13 @@
private static final int LINE_COUNT = 6;
private static final int LARGER_THAN_LINE_COUNT = 50;
+ private static final String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing "
+ + "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad "
+ + "minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
+ + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
+ + "cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non "
+ + "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+
/* the first line must have one tab. the others not. totally 6 lines
*/
private static final CharSequence LAYOUT_TEXT = "CharSe\tq\nChar"
@@ -203,8 +218,8 @@
// setBreakStrategy, setHyphenationFrequency, setIncludePad, and setIndents.
StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
- builder.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY);
- builder.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_FULL);
+ builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
+ builder.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
builder.setIncludePad(true);
builder.setIndents(null, null);
StaticLayout layout = builder.build();
@@ -213,6 +228,38 @@
}
@Test
+ public void testSetLineSpacing_whereLineEndsWithNextLine() {
+ final float spacingAdd = 10f;
+ final float spacingMult = 3f;
+
+ // two lines of text, with line spacing, first line will have the spacing, but last line
+ // wont have the spacing
+ final String tmpText = "a\nb";
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(tmpText, 0, tmpText.length(),
+ mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setLineSpacing(spacingAdd, spacingMult).setIncludePad(false);
+ final StaticLayout comparisonLayout = builder.build();
+
+ assertEquals(2, comparisonLayout.getLineCount());
+ final int heightWithLineSpacing = comparisonLayout.getLineBottom(0)
+ - comparisonLayout.getLineTop(0);
+ final int heightWithoutLineSpacing = comparisonLayout.getLineBottom(1)
+ - comparisonLayout.getLineTop(1);
+ assertTrue(heightWithLineSpacing > heightWithoutLineSpacing);
+
+ final String text = "a\n";
+ // build the layout to be tested
+ builder = StaticLayout.Builder.obtain("a\n", 0, text.length(), mDefaultPaint,
+ DEFAULT_OUTER_WIDTH);
+ builder.setLineSpacing(spacingAdd, spacingMult).setIncludePad(false);
+ final StaticLayout layout = builder.build();
+
+ assertEquals(comparisonLayout.getLineCount(), layout.getLineCount());
+ assertEquals(heightWithLineSpacing, layout.getLineBottom(0) - layout.getLineTop(0));
+ assertEquals(heightWithoutLineSpacing, layout.getLineBottom(1) - layout.getLineTop(1));
+ }
+
+ @Test
public void testBuilder_setJustificationMode() {
StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
@@ -222,6 +269,7 @@
// without causing any exceptions.
assertNotNull(layout);
}
+
/*
* Get the line number corresponding to the specified vertical position.
* If you ask for a position above 0, you get 0. above 0 means pixel above the fire line
@@ -1171,6 +1219,91 @@
assertTrue(layout.getEllipsisStart(0) != 0);
}
+ @Test
+ public void testEllipsize_retryEnd() {
+ final float size = 100.0f;
+
+ final int allocatedWidth = (int) (3.4f * size);
+ final String text = "aaaa";
+ final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(),
+ getTextPaintForEllipsize(size), allocatedWidth)
+ .setEllipsize(TextUtils.TruncateAt.END)
+ .setEllipsizedWidth(allocatedWidth)
+ .setMaxLines(1)
+ .build();
+ assertEquals(1, layout.getEllipsisStart(0)); // After the first 'a'
+ assertEquals(3, layout.getEllipsisCount(0));
+ }
+
+ @Test
+ public void testEllipsize_retryEndRtl() {
+ final float size = 100.0f;
+
+ final int allocatedWidth = (int) (3.4f * size);
+ final String text = "\u202Eaaaa"; // U+202E is the RIGHT-TO-LEFT OVERRIDE.
+ final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(),
+ getTextPaintForEllipsize(size), allocatedWidth)
+ .setEllipsize(TextUtils.TruncateAt.END)
+ .setEllipsizedWidth(allocatedWidth)
+ .setMaxLines(1)
+ .build();
+ assertEquals(2, layout.getEllipsisStart(0)); // After the first 'a'
+ assertEquals(3, layout.getEllipsisCount(0));
+ }
+
+ @Test
+ public void testEllipsize_retryStart() {
+ final float size = 100.0f;
+
+ final int allocatedWidth = (int) (3.4f * size);
+ final String text = "aaaa";
+ final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(),
+ getTextPaintForEllipsize(size), allocatedWidth)
+ .setEllipsize(TextUtils.TruncateAt.START)
+ .setEllipsizedWidth(allocatedWidth)
+ .setMaxLines(1)
+ .build();
+ assertEquals(0, layout.getEllipsisStart(0));
+ assertEquals(3, layout.getEllipsisCount(0));
+ }
+
+ @Test
+ public void testEllipsize_retryMiddle() {
+ final float size = 100.0f;
+
+ final int allocatedWidth = (int) (5.9f * size);
+ final String text = "aaaaaa";
+ final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(),
+ getTextPaintForEllipsize(size), allocatedWidth)
+ .setEllipsize(TextUtils.TruncateAt.MIDDLE)
+ .setEllipsizedWidth(allocatedWidth)
+ .setMaxLines(1)
+ .build();
+ final int ellipsisStart = layout.getEllipsisStart(0);
+ assertTrue(ellipsisStart == 1 || ellipsisStart == 2);
+ assertEquals(3, layout.getEllipsisCount(0));
+ }
+
+ private TextPaint getTextPaintForEllipsize(float size) {
+ // The font used in this method has two glyphs defined for "a" and ellipsis. Both are one
+ // em wide. But the glyphs are kerned: whenever the "a" is followed or preceded by an
+ // ellipsis, half an em is added between them as kerning. This means that:
+ // "aaaa" is 4 ems wide,
+ // "aaa…" is 4.5 ems wide,
+ // "aa…" is 3.5 ems wide,
+ // "a…" is 2.5 ems wide,
+ // "aa…aa" is 6 ems wide,
+ // "aa…a" is 5 ems wide,
+ // "a…aa" is 5 ems wide,
+ // "a…a" is 4 ems wide,
+ // "…a" is 2.5 ems wide.
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(Typeface.createFromAsset(
+ InstrumentationRegistry.getTargetContext().getAssets(), "ellipsis_test_font.ttf"));
+ paint.setTextSize(size);
+ return paint;
+ }
+
@Test(expected = IndexOutOfBoundsException.class)
public void testGetPrimary_shouldFail_whenOffsetIsOutOfBounds_withSpannable() {
final String text = "1\n2\n3";
@@ -1190,4 +1323,78 @@
.setEllipsize(TruncateAt.END).build();
layout.getPrimaryHorizontal(layout.getText().length());
}
+
+ // TODO: Re-enable once http://b/65207701 is fixed.
+ @Test
+ @Suppress
+ public void testGetLineWidth() {
+ final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
+ final int lineWidth = (int) (wholeWidth / 10.0f); // Make 10 lines per paragraph.
+ final String multiParaTestString =
+ LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM;
+ final Layout layout = StaticLayout.Builder.obtain(multiParaTestString, 0,
+ multiParaTestString.length(), mDefaultPaint, lineWidth)
+ .build();
+ for (int i = 0; i < layout.getLineCount(); i++) {
+ assertTrue(layout.getLineWidth(i) <= lineWidth);
+ }
+ }
+
+ // TODO: Re-enable once http://b/65207701 is fixed.
+ @Test
+ @Suppress
+ public void testIndent() {
+ final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
+ final int lineWidth = (int) (wholeWidth / 10.0f); // Make 10 lines per paragraph.
+ final int indentWidth = (int) (lineWidth * 0.3f); // Make 30% indent.
+ final String multiParaTestString =
+ LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM + "\n" + LOREM_IPSUM;
+ final Layout layout = StaticLayout.Builder.obtain(multiParaTestString, 0,
+ multiParaTestString.length(), mDefaultPaint, lineWidth)
+ .setIndents(new int[] { indentWidth }, null)
+ .build();
+ for (int i = 0; i < layout.getLineCount(); i++) {
+ assertTrue(layout.getLineWidth(i) <= lineWidth - indentWidth);
+ }
+ }
+
+ private static Bitmap drawToBitmap(Layout l) {
+ final Bitmap bmp = Bitmap.createBitmap(l.getWidth(), l.getHeight(), Bitmap.Config.RGB_565);
+ final Canvas c = new Canvas(bmp);
+
+ c.save();
+ c.translate(0, 0);
+ l.draw(c);
+ c.restore();
+ return bmp;
+ }
+
+ @Test
+ public void testPremeasured() {
+ final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
+ final int lineWidth = (int) (wholeWidth / 10.0f); // Make 10 lines per paragraph.
+
+ final ColorStateList textColor = ColorStateList.valueOf(0x88FF0000);
+ final TextAppearanceSpan span = new TextAppearanceSpan(
+ "serif", Typeface.BOLD, 64 /* text size */, textColor, textColor);
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(
+ LOREM_IPSUM + "\n" + LOREM_IPSUM);
+ ssb.setSpan(span, 0, LOREM_IPSUM.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ final Layout layout = StaticLayout.Builder.obtain(ssb, 0, ssb.length(), mDefaultPaint,
+ lineWidth).build();
+
+ final PremeasuredText premeasuredText = PremeasuredText.build(ssb, mDefaultPaint,
+ TextDirectionHeuristics.FIRSTSTRONG_LTR);
+ final Layout premLayout = StaticLayout.Builder.obtain(premeasuredText, 0,
+ premeasuredText.length(), mDefaultPaint, lineWidth)
+ .setTextDirection(TextDirectionHeuristics.FIRSTSTRONG_LTR).build();
+
+ assertEquals(layout.getHeight(), premLayout.getHeight(), 0.0f);
+
+ final Bitmap bmp = drawToBitmap(layout);
+ final Bitmap premBmp = drawToBitmap(premLayout);
+
+ assertTrue(bmp.sameAs(premBmp)); // Need to be pixel perfect.
+ }
}
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index 2fd4e59..b0848bb 100644
--- a/tests/tests/text/src/android/text/cts/TextUtilsTest.java
+++ b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
@@ -706,6 +706,83 @@
}
}
+ @Test
+ public void testEllipsize_retryEnd() {
+ // When we try to ellipsize "aaaa" into a thinner 3.4 em space, we originally think
+ // "aa…" would fit, but after measuring the new text, we find that it doesn't and we need
+ // to retry.
+ final float size = 100.0f;
+ final String text = "aaaa";
+
+ final CharSequence ellipsized = TextUtils.ellipsize(text, getTextPaintForEllipsize(size),
+ 3.4f * size,
+ TextUtils.TruncateAt.END, false /* preserveLength */, null /* callback */);
+ assertEquals("a\u2026", ellipsized.toString());
+ }
+
+ @Test
+ public void testEllipsize_retryEndRtl() {
+ // When we try to ellipsize "aaaa" into a thinner 3.4 em space, we originally think
+ // "aa…" would fit, but after measuring the new text, we find that it doesn't and we need
+ // to retry.
+ final float size = 100.0f;
+ final String text = "\u202Eaaaa"; // U+202E is the RIGHT-TO-LEFT OVERRIDE.
+
+ final CharSequence ellipsized = TextUtils.ellipsize(text, getTextPaintForEllipsize(size),
+ 3.4f * size,
+ TextUtils.TruncateAt.END, false /* preserveLength */, null /* callback */);
+ assertEquals("\u202Ea\u2026", ellipsized.toString());
+ }
+
+ @Test
+ public void testEllipsize_retryStart() {
+ // When we try to ellipsize "aaaa" into a thinner 3.4 em space, we originally think
+ // "…aa" would fit, but after measuring the new text, we find that it doesn't and we need
+ // to retry.
+ final float size = 100.0f;
+ final String text = "aaaa";
+
+ final CharSequence ellipsized = TextUtils.ellipsize(text, getTextPaintForEllipsize(size),
+ 3.4f * size,
+ TextUtils.TruncateAt.START, false /* preserveLength */, null /* callback */);
+ assertEquals("\u2026a", ellipsized.toString());
+ }
+
+ @Test
+ public void testEllipsize_retryMiddle() {
+ // When we try to ellipsize "aaaaaa" into a thinner 5.9 em space, we originally think
+ // "aa…aa" would fit, but after measuring the new text, we find that it doesn't and we need
+ // to retry.
+ final float size = 100.0f;
+ final String text = "aaaaaa";
+
+ final CharSequence ellipsized = TextUtils.ellipsize(text, getTextPaintForEllipsize(size),
+ 5.9f * size,
+ TextUtils.TruncateAt.MIDDLE, false /* preserveLength */, null /* callback */);
+ final String ellipsizedString = ellipsized.toString();
+ assertTrue("aa\u2026a".equals(ellipsizedString) || "a\u2026aa".equals(ellipsizedString));
+ }
+
+ private TextPaint getTextPaintForEllipsize(float size) {
+ // The font used in this method has two glyphs defined for "a" and ellipsis. Both are one
+ // em wide. But the glyphs are kerned: whenever the "a" is followed or preceded by an
+ // ellipsis, half an em is added between them as kerning. This means that:
+ // "aaaa" is 4 ems wide,
+ // "aaa…" is 4.5 ems wide,
+ // "aa…" is 3.5 ems wide,
+ // "a…" is 2.5 ems wide,
+ // "aa…aa" is 6 ems wide,
+ // "aa…a" is 5 ems wide,
+ // "a…aa" is 5 ems wide,
+ // "a…a" is 4 ems wide,
+ // "…a" is 2.5 ems wide.
+ final TextPaint paint = new TextPaint();
+ paint.setTypeface(Typeface.createFromAsset(
+ InstrumentationRegistry.getTargetContext().getAssets(), "ellipsis_test_font.ttf"));
+ paint.setTextSize(size);
+ return paint;
+ }
+
/**
* Get a blank string which is filled up by '\uFEFF'.
*
@@ -1614,6 +1691,8 @@
spannableStringTokens.add(new SpannableString("span 2"));
spannableStringTokens.add(new SpannableString("span 3"));
assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
+
+ assertEquals("", TextUtils.join("|", new ArrayList<CharSequence>()));
}
@Test(expected=NullPointerException.class)
@@ -1636,6 +1715,8 @@
new SpannableString("span 2"),
new SpannableString("span 3") };
assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
+
+ assertEquals("", TextUtils.join("|", new String[0]));
}
@Test(expected=NullPointerException.class)
diff --git a/tests/tests/text/src/android/text/format/cts/DateFormatTest.java b/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
index 13c9aa2..50c97b2 100644
--- a/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
+++ b/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
@@ -18,12 +18,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.UiAutomation;
import android.content.Context;
+import android.content.res.Configuration;
+import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Settings;
import android.support.annotation.NonNull;
@@ -312,6 +315,27 @@
}
}
+ @Test
+ public void test_ContextLocaleIsUsed() {
+ final Locale oldLocale = Locale.getDefault();
+
+ try {
+ Date date = new Date(YEAR_FROM_1900, MONTH, DAY);
+ Locale.setDefault(Locale.FRANCE);
+ final String javaResult = java.text.DateFormat.getDateInstance(
+ java.text.DateFormat.LONG).format(date);
+
+ final Configuration config = new Configuration();
+ config.setLocales(new LocaleList(Locale.JAPAN));
+ final Context context = mContext.createConfigurationContext(config);
+ final String androidResult = DateFormat.getLongDateFormat(context).format(date);
+
+ assertNotEquals(javaResult, androidResult);
+ } finally {
+ Locale.setDefault(oldLocale);
+ }
+ }
+
@NonNull
private String getTimeFormat() throws IOException {
return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
diff --git a/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java b/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java
index 9c96519..4f26aea 100644
--- a/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java
+++ b/tests/tests/text/src/android/text/method/cts/KeyListenerCtsActivity.java
@@ -18,7 +18,6 @@
import android.app.Activity;
import android.os.Bundle;
-import android.os.SystemClock;
import android.text.cts.R;
import android.text.method.BaseKeyListener;
import android.text.method.DateKeyListener;
@@ -29,7 +28,6 @@
import android.text.method.QwertyKeyListener;
import android.text.method.TextKeyListener;
import android.text.method.TimeKeyListener;
-import android.util.Log;
/**
* This Activity is used for testing:
@@ -55,47 +53,9 @@
*/
public class KeyListenerCtsActivity extends Activity {
- private boolean mHasWindowFocus = false;
- private final Object mHasWindowFocusLock = new Object();
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.keylistener_layout);
}
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- if (!hasFocus) {
- Log.w("KeyListenerCtsActivity", "KeyListenerCtsActivity lost window focus");
- }
- synchronized(mHasWindowFocusLock) {
- mHasWindowFocus = hasFocus;
- mHasWindowFocusLock.notify();
- }
- }
-
- /**
- * Blocks the calling thread until the {@link KeyListenerCtsActivity} has window focus or the
- * specified duration (in milliseconds) has passed.
- */
- public boolean waitForWindowFocus(long durationMillis) {
- long elapsedMillis = SystemClock.elapsedRealtime();
- synchronized(mHasWindowFocusLock) {
- mHasWindowFocus = hasWindowFocus();
- while (!mHasWindowFocus && durationMillis > 0) {
- long newElapsedMillis = SystemClock.elapsedRealtime();
- durationMillis -= (newElapsedMillis - elapsedMillis);
- elapsedMillis = newElapsedMillis;
- if (durationMillis > 0) {
- try {
- mHasWindowFocusLock.wait(durationMillis);
- } catch (InterruptedException e) {
- }
- }
- }
- return mHasWindowFocus;
- }
- }
}
diff --git a/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java b/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java
index 8d47c57..36f92e2 100644
--- a/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java
+++ b/tests/tests/text/src/android/text/method/cts/KeyListenerTestCase.java
@@ -16,40 +16,86 @@
package android.text.method.cts;
+import static android.provider.Settings.System.TEXT_AUTO_CAPS;
+
+import android.app.AppOpsManager;
import android.app.Instrumentation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.text.cts.R;
import android.text.method.KeyListener;
+import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
import org.junit.Before;
import org.junit.Rule;
+import java.io.IOException;
+
/**
* Base class for various KeyListener tests.
*/
public abstract class KeyListenerTestCase {
+ private static final String TAG = "KeyListenerTestCase";
+
protected KeyListenerCtsActivity mActivity;
protected Instrumentation mInstrumentation;
protected EditText mTextView;
+ private int mAutoCapSetting;
@Rule
public ActivityTestRule<KeyListenerCtsActivity> mActivityRule =
new ActivityTestRule<>(KeyListenerCtsActivity.class);
@Before
- public void setup() {
+ public void setup() throws IOException {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mActivity = mActivityRule.getActivity();
- mTextView = (EditText) mActivity.findViewById(R.id.keylistener_textview);
+ mTextView = mActivity.findViewById(R.id.keylistener_textview);
PollingCheck.waitFor(5000, mActivity::hasWindowFocus);
}
+ protected void enableAutoCapSettings() throws IOException {
+ grantWriteSettingsPermission();
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final Context context = instrumentation.getContext();
+ instrumentation.runOnMainSync(() -> {
+ final ContentResolver resolver = context.getContentResolver();
+ mAutoCapSetting = Settings.System.getInt(resolver, TEXT_AUTO_CAPS, 1);
+ try {
+ Settings.System.putInt(resolver, TEXT_AUTO_CAPS, 1);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Cannot set TEXT_AUTO_CAPS to 1", e);
+ // ignore
+ }
+ });
+ instrumentation.waitForIdleSync();
+ }
+
+ protected void resetAutoCapSettings() throws IOException {
+ grantWriteSettingsPermission();
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final Context context = instrumentation.getContext();
+ instrumentation.runOnMainSync(() -> {
+ final ContentResolver resolver = context.getContentResolver();
+ try {
+ Settings.System.putInt(resolver, TEXT_AUTO_CAPS, mAutoCapSetting);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Cannot set TEXT_AUTO_CAPS to previous value", e);
+ // ignore
+ }
+ });
+ instrumentation.waitForIdleSync();
+ }
+
/**
* Synchronously sets mTextView's key listener on the UI thread.
*/
@@ -63,4 +109,10 @@
return new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN, keycode,
0 /* repeat */, metaState);
}
+
+ private void grantWriteSettingsPermission() throws IOException {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "appops set " + mActivity.getPackageName() + " "
+ + AppOpsManager.OPSTR_WRITE_SETTINGS + " allow");
+ }
}
diff --git a/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java b/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java
index c73b7fa..206c6a5 100644
--- a/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java
+++ b/tests/tests/text/src/android/text/method/cts/MultiTapKeyListenerTest.java
@@ -39,9 +39,13 @@
import android.view.KeyEvent;
import android.widget.TextView.BufferType;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MultiTapKeyListenerTest extends KeyListenerTestCase {
@@ -50,6 +54,17 @@
*/
private static final long TIME_OUT = 3000;
+ @Before
+ public void setup() throws IOException {
+ super.setup();
+ enableAutoCapSettings();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ resetAutoCapSettings();
+ }
+
@Test
public void testConstructor() {
new MultiTapKeyListener(Capitalize.NONE, true);
diff --git a/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java b/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java
index c2e684a..c527257 100644
--- a/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java
+++ b/tests/tests/text/src/android/text/method/cts/QwertyKeyListenerTest.java
@@ -32,12 +32,28 @@
import android.view.KeyEvent;
import android.widget.TextView.BufferType;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+
@MediumTest
@RunWith(AndroidJUnit4.class)
public class QwertyKeyListenerTest extends KeyListenerTestCase {
+
+ @Before
+ public void setup() throws IOException {
+ super.setup();
+ enableAutoCapSettings();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ resetAutoCapSettings();
+ }
+
@Test
public void testConstructor() {
new QwertyKeyListener(Capitalize.NONE, false);
diff --git a/tests/tests/theme/Android.mk b/tests/tests/theme/Android.mk
index 71d576d..ff8536b 100644
--- a/tests/tests/theme/Android.mk
+++ b/tests/tests/theme/Android.mk
@@ -24,7 +24,9 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/theme/AndroidTest.xml b/tests/tests/theme/AndroidTest.xml
index 39da798..95a7a88 100644
--- a/tests/tests/theme/AndroidTest.xml
+++ b/tests/tests/theme/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Theme test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 66ceb5d..0f00913 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for Toast test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/transition/Android.mk b/tests/tests/transition/Android.mk
index cba0b45..0937fc1 100644
--- a/tests/tests/transition/Android.mk
+++ b/tests/tests/transition/Android.mk
@@ -31,8 +31,7 @@
android-common \
compatibility-device-util \
ctstestrunner \
- platform-test-annotations \
- legacy-android-test
+ platform-test-annotations
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/tests/transition/AndroidTest.xml b/tests/tests/transition/AndroidTest.xml
index 4632cbe..e75e8de 100644
--- a/tests/tests/transition/AndroidTest.xml
+++ b/tests/tests/transition/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Transition test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/tv/Android.mk b/tests/tests/tv/Android.mk
index 30429d3..24cf139 100644
--- a/tests/tests/tv/Android.mk
+++ b/tests/tests/tv/Android.mk
@@ -27,7 +27,7 @@
LOCAL_PACKAGE_NAME := CtsTvTestCases
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base.stubs
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
diff --git a/tests/tests/uiautomation/Android.mk b/tests/tests/uiautomation/Android.mk
index eb6ed68..347c55f 100644
--- a/tests/tests/uiautomation/Android.mk
+++ b/tests/tests/uiautomation/Android.mk
@@ -23,7 +23,9 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ub-uiautomator legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ub-uiautomator
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/uiautomation/AndroidTest.xml b/tests/tests/uiautomation/AndroidTest.xml
index 183a08e..c4bcdd6 100644
--- a/tests/tests/uiautomation/AndroidTest.xml
+++ b/tests/tests/uiautomation/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS UI Automation test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
index cf46157..ca598bc 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestA11yService.java
@@ -49,7 +49,7 @@
public boolean isConnected() {
try {
- if (getRootInActiveWindow() == null) {
+ if (getServiceInfo() == null) {
return false;
}
return true;
diff --git a/tests/tests/uidisolation/Android.mk b/tests/tests/uidisolation/Android.mk
index e9ee3e9..a820c95 100644
--- a/tests/tests/uidisolation/Android.mk
+++ b/tests/tests/uidisolation/Android.mk
@@ -24,9 +24,13 @@
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctstestserver legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctstestserver
-LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ org.apache.http.legacy \
+ android.test.runner.stubs \
+ android.test.base.stubs \
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/uidisolation/AndroidTest.xml b/tests/tests/uidisolation/AndroidTest.xml
index 02df495..0a83f12 100644
--- a/tests/tests/uidisolation/AndroidTest.xml
+++ b/tests/tests/uidisolation/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS UID Isolation test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="security" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/uirendering/Android.mk b/tests/tests/uirendering/Android.mk
index 9cc07b9..f94b611 100644
--- a/tests/tests/uirendering/Android.mk
+++ b/tests/tests/uirendering/Android.mk
@@ -31,8 +31,7 @@
ctsdeviceutillegacy \
ctstestrunner \
mockito-target-minus-junit4 \
- android-support-test \
- legacy-android-test
+ android-support-test
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/uirendering/res/drawable/circle.xml b/tests/tests/uirendering/res/drawable/circle.xml
new file mode 100644
index 0000000..9d47a8a
--- /dev/null
+++ b/tests/tests/uirendering/res/drawable/circle.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="90px"
+ android:width="90px"
+ android:viewportHeight="1"
+ android:viewportWidth="1" >
+
+ <group>
+ <path
+ android:name="box0"
+ android:pathData="m0,0.5a0.5,0.5 0 1,0 1,0a0.5,0.5 0 1,0 -1,0"
+ android:fillColor="#FF0000" />
+ </group>
+</vector>
\ No newline at end of file
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AlphaBlendTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AlphaBlendTest.java
new file mode 100644
index 0000000..8c777f8
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AlphaBlendTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.testclasses;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AlphaBlendTest extends ActivityTestBase {
+
+ class ViewWithAlpha extends View {
+ public ViewWithAlpha(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawColor(Color.RED);
+ canvas.drawColor(Color.BLUE);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+ }
+
+ /*
+ * The following test verifies that a RED and BLUE paints on a non-overlapping view with a 0.5f
+ * alpha blends correctly with a BLACK parent (without using an offscreen surface).
+ */
+ @Test
+ public void testBlendAlphaNonOverlappingView() {
+
+ ViewInitializer initializer = new ViewInitializer() {
+
+ @Override
+ public void initializeView(View view) {
+ FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+ root.setBackgroundColor(Color.BLACK);
+
+ final ViewWithAlpha child = new ViewWithAlpha(view.getContext());
+
+ child.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+ child.setAlpha(0.5f);
+ root.addView(child);
+ }
+ };
+
+ createTest()
+ .addLayout(R.layout.frame_layout, initializer, true)
+ .runWithVerifier(new ColorVerifier(0xff40007f));
+ }
+}
+
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterTests.java
new file mode 100644
index 0000000..4fd227d
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.testclasses;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ColorFilterTests extends ActivityTestBase {
+
+ @Test
+ public void testColorMatrix() {
+ createTest()
+ .addCanvasClient((canvas, width, height) -> {
+ Bitmap whiteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ whiteBitmap.eraseColor(Color.WHITE);
+
+ Paint paint = new Paint();
+ canvas.drawBitmap(whiteBitmap, 0, 0, paint);
+
+ paint.setAntiAlias(true);
+ paint.setColorFilter(new ColorMatrixColorFilter(new float[] {
+ -1, 0, 0, 0, 255,
+ 0, -1, 0, 0, 255,
+ 0, 0, -1, 0, 255,
+ 0, 0, 0, 1, 0
+ }));
+ canvas.drawBitmap(whiteBitmap, 0, 0, paint);
+
+ }, true)
+ .runWithVerifier(new ColorVerifier(Color.BLACK));
+ }
+
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index ed0110a..ae8f57e 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -47,6 +47,7 @@
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -294,9 +295,9 @@
.runWithVerifier(new RectVerifier(Color.WHITE, Color.GREEN, new Rect(40, 40, 70, 70)));
}
- // Note: This test will fail for Skia pipeline, but that is OK.
- // TODO: delete this test when Skia pipeline is default and modify next test
+ // STOPSHIP: delete this test when Skia pipeline ships as the default and modify next test
// testSaveLayerUnclippedWithColorFilterSW to run for both HW and SW
+ @Ignore
@Test
public void testSaveLayerUnclippedWithColorFilterHW() {
// verify that HW can draw nested unclipped layers with chained color filters
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
index ffb263d..f4db101 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/VectorDrawableTests.java
@@ -16,17 +16,29 @@
package android.uirendering.cts.testclasses;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.uirendering.cts.R;
import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+import android.widget.FrameLayout;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import android.animation.Animator;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -44,5 +56,94 @@
.runWithVerifier(
new RectVerifier(Color.WHITE, Color.RED, new Rect(0, 0, 25, 25)));
}
+
+ class VectorDrawableView extends View {
+ private VectorDrawable mVd;
+ private Rect mVdBounds;
+
+ public VectorDrawableView(Context context) {
+ super(context);
+ mVd = (VectorDrawable) context.getResources().getDrawable(R.drawable.circle, null);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mVd.setBounds(mVdBounds);
+ mVd.draw(canvas);
+ }
+
+ public void setVDSize(Rect vdBounds) {
+ mVdBounds = vdBounds;
+ }
+ }
+
+ /*
+ * The following test verifies that VectorDrawable.setBounds invalidates the bitmap cache.
+ */
+ @Test
+ public void testInvalidateCache() {
+ CountDownLatch testFinishedFence = new CountDownLatch(1);
+
+ ViewInitializer initializer = new ViewInitializer() {
+ ValueAnimator mAnimator;
+
+ @Override
+ public void initializeView(View view) {
+ FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+ root.setBackgroundColor(Color.BLUE);
+
+ final VectorDrawableView child = new VectorDrawableView(view.getContext());
+
+ child.setLayoutParams(new FrameLayout.LayoutParams(ActivityTestBase.TEST_WIDTH,
+ ActivityTestBase.TEST_HEIGHT));
+ // VectorDrawable is a red circle drawn on top of a blue background.
+ // The first frame has VectorDrawable size set to 1x1 pixels, which deforms
+ // the red circle into a 1x1 red-ish square.
+ // An animation grows VectorDrawable bounds from 0x0 to 90x90. If VD cache is
+ // refreshed, then we should see a red circle on top of a blue background.
+ // If VD cache is stale, then VD will upscale the original 1x1 cached image to
+ // 90x90 red-ish square.
+ // At the end of the animation, we verify the color of top left pixel, which should
+ // be a blue background pixel.
+ child.setVDSize(new Rect(0, 0, 2, 2)); //first draw with VD size set to 1x1 pixels.
+ root.addView(child);
+
+ mAnimator = ValueAnimator.ofFloat(0, 1);
+ mAnimator.setRepeatCount(0);
+ mAnimator.setDuration(400);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float progress = (float) mAnimator.getAnimatedValue();
+ child.setVDSize(new Rect(0, 0, (int)(progress*child.getWidth()),
+ (int)(progress*child.getHeight())));
+ child.invalidate();
+ }
+ });
+ mAnimator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ testFinishedFence.countDown();
+ }
+ });
+
+ mAnimator.start();
+ }
+
+ @Override
+ public void teardownView() {
+ mAnimator.cancel();
+ }
+ };
+
+ createTest()
+ .addLayout(R.layout.frame_layout, initializer, true, testFinishedFence)
+ .runWithVerifier(new SamplePointVerifier(
+ new Point[] { new Point(0, 0) },
+ new int[] { 0xff0000ff }
+ ));
+ }
}
diff --git a/tests/tests/util/Android.mk b/tests/tests/util/Android.mk
index ed7a61c..0ad2452 100644
--- a/tests/tests/util/Android.mk
+++ b/tests/tests/util/Android.mk
@@ -27,8 +27,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-annotations \
android-support-test \
- ctstestrunner \
- legacy-android-test
+ ctstestrunner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/tests/util/AndroidTest.xml b/tests/tests/util/AndroidTest.xml
index b022356..8be8204 100644
--- a/tests/tests/util/AndroidTest.xml
+++ b/tests/tests/util/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Util test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 533d98b..a678561 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -26,7 +26,7 @@
LOCAL_MULTILIB := both
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
@@ -35,8 +35,7 @@
ctstestrunner \
mockito-target-minus-junit4 \
platform-test-annotations \
- ub-uiautomator \
- legacy-android-test
+ ub-uiautomator
LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 7c35627..6cddaf3 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -288,15 +288,6 @@
</intent-filter>
</activity>
- <activity android:name="android.view.cts.PanicPressBackActivity"
- android:screenOrientation="locked"
- android:label="PanicPressBackActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<activity android:name="android.view.cts.DragDropActivity"
android:screenOrientation="portrait"
android:label="DragDropActivity">
@@ -348,6 +339,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity android:name="android.view.cts.KeyEventInterceptTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.cts.TouchDelegateTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/AndroidTest.xml b/tests/tests/view/AndroidTest.xml
index 9ed52bc..0ae9f42 100644
--- a/tests/tests/view/AndroidTest.xml
+++ b/tests/tests/view/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS View test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/view/res/layout/focus_handling_layout.xml b/tests/tests/view/res/layout/focus_handling_layout.xml
index 6966e53..d2f7778 100644
--- a/tests/tests/view/res/layout/focus_handling_layout.xml
+++ b/tests/tests/view/res/layout/focus_handling_layout.xml
@@ -22,14 +22,14 @@
<View
android:id="@+id/view1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="50dp"
+ android:layout_height="30dp"
android:text="@string/id_ok"/>
<View
android:id="@+id/view2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="50dp"
+ android:layout_height="30dp"
android:layout_toRightOf="@id/view1"
android:layout_alignTop="@id/view1"
android:nextFocusLeft="@id/view1"
@@ -37,8 +37,8 @@
<View
android:id="@+id/view3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="50dp"
+ android:layout_height="30dp"
android:layout_below="@id/view1"
android:layout_alignLeft="@id/view1"
android:nextFocusUp="@id/view1"
@@ -46,8 +46,8 @@
<View
android:id="@+id/view4"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="50dp"
+ android:layout_height="30dp"
android:layout_toRightOf="@id/view3"
android:layout_alignTop="@id/view3"
android:layout_alignRight="@id/view2"
@@ -57,8 +57,8 @@
<LinearLayout
android:id="@+id/auto_test_area"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
android:orientation="vertical"
android:layout_below="@id/view4"
android:layout_alignParentLeft="true">
diff --git a/tests/tests/view/res/layout/key_fallback_layout.xml b/tests/tests/view/res/layout/key_fallback_layout.xml
new file mode 100644
index 0000000..5d5cef1
--- /dev/null
+++ b/tests/tests/view/res/layout/key_fallback_layout.xml
@@ -0,0 +1,58 @@
+<?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:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <Button
+ android:id="@+id/higher_in_normal"
+ android:translationZ="3dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/last_in_normal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/higher_group"
+ android:translationZ="1dp"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/lower_in_higher"
+ android:translationZ="-2dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/last_in_higher"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/last_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/view/res/layout/touch_delegate_test_activity_layout.xml b/tests/tests/view/res/layout/touch_delegate_test_activity_layout.xml
new file mode 100644
index 0000000..0780046
--- /dev/null
+++ b/tests/tests/view/res/layout/touch_delegate_test_activity_layout.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ />
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/DragDropTest.java b/tests/tests/view/src/android/view/cts/DragDropTest.java
index 39ae2d3..d74acef 100644
--- a/tests/tests/view/src/android/view/cts/DragDropTest.java
+++ b/tests/tests/view/src/android/view/cts/DragDropTest.java
@@ -25,6 +25,8 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
@@ -35,8 +37,6 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.internal.util.ArrayUtils;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -44,9 +44,10 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Objects;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
@RunWith(AndroidJUnit4.class)
public class DragDropTest {
@@ -64,60 +65,81 @@
private CountDownLatch mStartReceived;
private CountDownLatch mEndReceived;
- private static boolean equal(ClipDescription d1, ClipDescription d2) {
- if ((d1 == null) != (d2 == null)) {
- return false;
- }
- if (d1 == null) {
+ /**
+ * Check whether two objects have the same binary data when dumped into Parcels
+ * @return True if the objects are equal
+ */
+ private static boolean compareParcelables(Parcelable obj1, Parcelable obj2) {
+ if (obj1 == null && obj2 == null) {
return true;
}
- return d1.getLabel().equals(d2.getLabel()) &&
- d1.getMimeTypeCount() == 1 && d2.getMimeTypeCount() == 1 &&
- d1.getMimeType(0).equals(d2.getMimeType(0));
- }
-
- private static boolean equal(ClipData.Item i1, ClipData.Item i2) {
- return Objects.equals(i1.getIntent(), i2.getIntent()) &&
- Objects.equals(i1.getHtmlText(), i2.getHtmlText()) &&
- Objects.equals(i1.getText(), i2.getText()) &&
- Objects.equals(i1.getUri(), i2.getUri());
- }
-
- private static boolean equal(ClipData d1, ClipData d2) {
- if ((d1 == null) != (d2 == null)) {
+ if (obj1 == null || obj2 == null) {
return false;
}
- if (d1 == null) {
- return true;
- }
- return equal(d1.getDescription(), d2.getDescription()) &&
- Objects.equals(d1.getIcon(), d2.getIcon()) &&
- d1.getItemCount() == 1 && d2.getItemCount() == 1 &&
- equal(d1.getItemAt(0), d2.getItemAt(0));
+ Parcel p1 = Parcel.obtain();
+ obj1.writeToParcel(p1, 0);
+ Parcel p2 = Parcel.obtain();
+ obj2.writeToParcel(p2, 0);
+ boolean result = Arrays.equals(p1.marshall(), p2.marshall());
+ p1.recycle();
+ p2.recycle();
+ return result;
}
- private static boolean equal(DragEvent ev1, DragEvent ev2) {
- return ev1.getAction() == ev2.getAction() &&
- ev1.getX() == ev2.getX() &&
- ev1.getY() == ev2.getY() &&
- equal(ev1.getClipData(), ev2.getClipData()) &&
- equal(ev1.getClipDescription(), ev2.getClipDescription()) &&
- Objects.equals(ev1.getDragAndDropPermissions(), ev2.getDragAndDropPermissions()) &&
- Objects.equals(ev1.getLocalState(), ev2.getLocalState()) &&
- ev1.getResult() == ev2.getResult();
- }
+ private static final ClipDescription sClipDescription =
+ new ClipDescription("TestLabel", new String[]{"text/plain"});
+ private static final ClipData sClipData =
+ new ClipData(sClipDescription, new ClipData.Item("TestText"));
+ private static final Object sLocalState = new Object(); // just check if null or not
class LogEntry {
- public View v;
- public DragEvent ev;
+ public View view;
- public LogEntry(View v, DragEvent ev) {
- this.v = v;
- this.ev = DragEvent.obtain(ev);
+ // Public DragEvent fields
+ public int action; // DragEvent.getAction()
+ public float x; // DragEvent.getX()
+ public float y; // DragEvent.getY()
+ public ClipData clipData; // DragEvent.getClipData()
+ public ClipDescription clipDescription; // DragEvent.getClipDescription()
+ public Object localState; // DragEvent.getLocalState()
+ public boolean result; // DragEvent.getResult()
+
+ LogEntry(View v, int action, float x, float y, ClipData clipData,
+ ClipDescription clipDescription, Object localState, boolean result) {
+ this.view = v;
+ this.action = action;
+ this.x = x;
+ this.y = y;
+ this.clipData = clipData;
+ this.clipDescription = clipDescription;
+ this.localState = localState;
+ this.result = result;
}
- public boolean eq(LogEntry other) {
- return v == other.v && equal(ev, other.ev);
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof LogEntry)) {
+ return false;
+ }
+ final LogEntry other = (LogEntry) obj;
+ return view == other.view && action == other.action
+ && x == other.x && y == other.y
+ && compareParcelables(clipData, other.clipData)
+ && compareParcelables(clipDescription, other.clipDescription)
+ && localState == other.localState
+ && result == other.result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DragEvent {action=").append(action).append(" x=").append(x).append(" y=")
+ .append(y).append(" result=").append(result).append("}")
+ .append(" @ ").append(view);
+ return sb.toString();
}
}
@@ -126,19 +148,18 @@
final private ArrayList<LogEntry> mActual = new ArrayList<LogEntry> ();
final private ArrayList<LogEntry> mExpected = new ArrayList<LogEntry> ();
- private static ClipDescription createClipDescription() {
- return new ClipDescription("TestLabel", new String[]{"text/plain"});
+ private static ClipData obtainClipData(int action) {
+ if (action == DragEvent.ACTION_DROP) {
+ return sClipData;
+ }
+ return null;
}
- private static ClipData createClipData() {
- return new ClipData(createClipDescription(), new ClipData.Item("TestText"));
- }
-
- static private DragEvent obtainDragEvent(int action, int x, int y, boolean result) {
- final ClipDescription description =
- action != DragEvent.ACTION_DRAG_ENDED ? createClipDescription() : null;
- final ClipData data = action == DragEvent.ACTION_DROP ? createClipData() : null;
- return DragEvent.obtain(action, x, y, null, description, data, null, result);
+ private static ClipDescription obtainClipDescription(int action) {
+ if (action == DragEvent.ACTION_DRAG_ENDED) {
+ return null;
+ }
+ return sClipDescription;
}
private void logEvent(View v, DragEvent ev) {
@@ -148,19 +169,23 @@
if (ev.getAction() == DragEvent.ACTION_DRAG_ENDED) {
mEndReceived.countDown();
}
- mActual.add(new LogEntry(v, ev));
+ mActual.add(new LogEntry(v, ev.getAction(), ev.getX(), ev.getY(), ev.getClipData(),
+ ev.getClipDescription(), ev.getLocalState(), ev.getResult()));
}
// Add expected event for a view, with zero coordinates.
private void expectEvent5(int action, int viewId) {
View v = mActivity.findViewById(viewId);
- mExpected.add(new LogEntry(v, obtainDragEvent(action, 0, 0, false)));
+ mExpected.add(new LogEntry(v, action, 0, 0, obtainClipData(action),
+ obtainClipDescription(action), sLocalState, false));
}
// Add expected event for a view.
- private void expectEndEvent(int viewId, int x, int y, boolean result) {
+ private void expectEndEvent(int viewId, float x, float y, boolean result) {
View v = mActivity.findViewById(viewId);
- mExpected.add(new LogEntry(v, obtainDragEvent(DragEvent.ACTION_DRAG_ENDED, x, y, result)));
+ int action = DragEvent.ACTION_DRAG_ENDED;
+ mExpected.add(new LogEntry(v, action, x, y, obtainClipData(action),
+ obtainClipDescription(action), sLocalState, result));
}
// Add expected successful-end event for a view.
@@ -173,9 +198,12 @@
private void expectEndEventFailure6(int viewId, int releaseViewId) {
View v = mActivity.findViewById(viewId);
View release = mActivity.findViewById(releaseViewId);
- int [] releaseLoc = release.getLocationOnScreen();
- mExpected.add(new LogEntry(v, obtainDragEvent(DragEvent.ACTION_DRAG_ENDED,
- releaseLoc[0] + 6, releaseLoc[1] + 6, false)));
+ int [] releaseLoc = new int[2];
+ release.getLocationOnScreen(releaseLoc);
+ int action = DragEvent.ACTION_DRAG_ENDED;
+ mExpected.add(new LogEntry(v, action,
+ releaseLoc[0] + 6, releaseLoc[1] + 6, obtainClipData(action),
+ obtainClipDescription(action), sLocalState, false));
}
// Add expected event for a view, with coordinates over view locationViewId, with the specified
@@ -183,11 +211,14 @@
private void expectEventWithOffset(int action, int viewId, int locationViewId, int offset) {
View v = mActivity.findViewById(viewId);
View locationView = mActivity.findViewById(locationViewId);
- int [] viewLocation = v.getLocationOnScreen();
- int [] locationViewLocation = locationView.getLocationOnScreen();
- mExpected.add(new LogEntry(v, obtainDragEvent(action,
+ int [] viewLocation = new int[2];
+ v.getLocationOnScreen(viewLocation);
+ int [] locationViewLocation = new int[2];
+ locationView.getLocationOnScreen(locationViewLocation);
+ mExpected.add(new LogEntry(v, action,
locationViewLocation[0] - viewLocation[0] + offset,
- locationViewLocation[1] - viewLocation[1] + offset, false)));
+ locationViewLocation[1] - viewLocation[1] + offset, obtainClipData(action),
+ obtainClipDescription(action), sLocalState, false));
}
private void expectEvent5(int action, int viewId, int locationViewId) {
@@ -203,7 +234,8 @@
private void injectMouseWithOffset(int viewId, int action, int offset) {
runOnMain(() -> {
View v = mActivity.findViewById(viewId);
- int [] destLoc = v.getLocationOnScreen();
+ int [] destLoc = new int [2];
+ v.getLocationOnScreen(destLoc);
long downTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, downTime, action,
destLoc[0] + offset, destLoc[1] + offset, 1);
@@ -237,8 +269,7 @@
StringBuilder sb = new StringBuilder();
for (int i = 0; i < log.size(); ++i) {
LogEntry e = log.get(i);
- sb.append("#").append(i + 1).append(": ").append(e.ev).append(" @ ").
- append(e.v.toString()).append('\n');
+ sb.append("#").append(i + 1).append(": ").append(e).append('\n');
}
return sb.toString();
}
@@ -263,7 +294,7 @@
}
for (int i = 0; i < mActual.size(); ++i) {
- if (!mActual.get(i).eq(mExpected.get(i))) {
+ if (!mActual.get(i).equals(mExpected.get(i))) {
failWithLogs("Actual event #" + (i + 1) + " is different from expected");
}
}
@@ -321,7 +352,7 @@
// Start drag.
View v = mActivity.findViewById(R.id.draggable);
assertTrue("Couldn't start drag",
- v.startDragAndDrop(createClipData(), new View.DragShadowBuilder(v), null, 0));
+ v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
});
try {
@@ -585,6 +616,11 @@
verifyEventLog();
}
+ private boolean drawableStateContains(int resourceId, int attr) {
+ return IntStream.of(mActivity.findViewById(resourceId).getDrawableState())
+ .anyMatch(x -> x == attr);
+ }
+
/**
* Tests that state_drag_hovered and state_drag_can_accept are set correctly.
*/
@@ -600,52 +636,38 @@
logEvent(v, ev);
return true;
});
- assertFalse(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_can_accept));
+ assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_can_accept));
});
startDrag();
runOnMain(() -> {
- assertFalse(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_hovered));
- assertTrue(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_can_accept));
+ assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
+ assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_can_accept));
});
// Move mouse into the view.
injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
runOnMain(() -> {
- assertTrue(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_hovered));
+ assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
});
// Move out.
injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
runOnMain(() -> {
- assertFalse(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_hovered));
+ assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
});
// Move in.
injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
runOnMain(() -> {
- assertTrue(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_hovered));
+ assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
});
// Release there.
injectMouse5(R.id.inner, MotionEvent.ACTION_UP);
runOnMain(() -> {
- assertFalse(ArrayUtils.contains(
- mActivity.findViewById(R.id.inner).getDrawableState(),
- android.R.attr.state_drag_hovered));
+ assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
});
}
}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/FocusFinderTest.java b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
index 0f2119e..2505111 100644
--- a/tests/tests/view/src/android/view/cts/FocusFinderTest.java
+++ b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
@@ -426,7 +426,6 @@
// While we don't want to test details, we should at least verify basic correctness
// like "left-to-right" ordering in well-behaved layouts
- assertEquals(mLayout.findFocus(), mTopLeft);
verifyNextFocus(mTopLeft, View.FOCUS_FORWARD, mTopRight);
verifyNextFocus(mTopRight, View.FOCUS_FORWARD, mBottomLeft);
verifyNextFocus(mBottomLeft, View.FOCUS_FORWARD, mBottomRight);
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
new file mode 100644
index 0000000..598552b
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Certain KeyEvents should never be delivered to apps. These keys are:
+ * KEYCODE_ASSIST
+ * KEYCODE_VOICE_ASSIST
+ * KEYCODE_HOME
+ * This test launches an Activity and inject KeyEvents with the corresponding key codes.
+ * The test will fail if any of these keys are received by the activity.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class KeyEventInterceptTest {
+ private KeyEventInterceptTestActivity mActivity;
+ private Instrumentation mInstrumentation;
+
+ @Rule
+ public ActivityTestRule<KeyEventInterceptTestActivity> mActivityRule =
+ new ActivityTestRule<>(KeyEventInterceptTestActivity.class);
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ PollingCheck.waitFor(mActivity::hasWindowFocus);
+ }
+
+ @Test
+ public void testKeyCodeAssist() {
+ testKey(KeyEvent.KEYCODE_ASSIST);
+ }
+
+ @Test
+ public void testKeyCodeVoiceAssist() {
+ testKey(KeyEvent.KEYCODE_VOICE_ASSIST);
+ }
+
+ @Test
+ public void testKeyCodeHome() {
+ testKey(KeyEvent.KEYCODE_HOME);
+ }
+
+ private void testKey(int keyCode) {
+ sendKey(keyCode);
+ assertKeyNotReceived();
+ }
+
+ private void sendKey(int keyCodeToSend) {
+ long downTime = SystemClock.uptimeMillis();
+ injectEvent(new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
+ keyCodeToSend, 0, 0));
+ injectEvent(new KeyEvent(downTime, downTime + 1, KeyEvent.ACTION_UP,
+ keyCodeToSend, 0, 0));
+ }
+
+ private void injectEvent(KeyEvent event) {
+ final UiAutomation automation = mInstrumentation.getUiAutomation();
+ automation.injectInputEvent(event, true);
+ event.recycle();
+ }
+
+ private void assertKeyNotReceived() {
+ try {
+ KeyEvent keyEvent = mActivity.mKeyEvents.poll(1, TimeUnit.SECONDS);
+ if (keyEvent == null) {
+ return;
+ }
+ fail("Should not have received " + KeyEvent.keyCodeToString(keyEvent.getKeyCode()));
+ } catch (InterruptedException ex) {
+ fail("BlockingQueue.poll(..) was unexpectedly interrupted");
+ }
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
new file mode 100644
index 0000000..18e0713
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTestActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.view.KeyEvent;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+
+public class KeyEventInterceptTestActivity extends Activity {
+ final BlockingQueue<KeyEvent> mKeyEvents = new LinkedBlockingDeque<>();
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ mKeyEvents.add(event);
+ return true;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ // Check this in case some spurious event with ACTION_UP is received
+ mKeyEvents.add(event);
+ return true;
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/PanicPressBackActivity.java b/tests/tests/view/src/android/view/cts/PanicPressBackActivity.java
deleted file mode 100644
index bb52491..0000000
--- a/tests/tests/view/src/android/view/cts/PanicPressBackActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.cts;
-
-import android.app.Activity;
-
-import java.util.concurrent.CountDownLatch;
-
-public class PanicPressBackActivity extends Activity {
-
- private boolean mWasPaused;
-
- public final CountDownLatch mWaitForPanicBackLatch = new CountDownLatch(1);
-
- @Override
- public void onBackPressed() {
- // Prevent back press from exiting app
- }
-
- @Override
- public void onPause() {
- mWaitForPanicBackLatch.countDown();
- super.onPause();
- }
-}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/PanicPressBackTest.java b/tests/tests/view/src/android/view/cts/PanicPressBackTest.java
deleted file mode 100644
index 8a0bf31..0000000
--- a/tests/tests/view/src/android/view/cts/PanicPressBackTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.cts;
-
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertTrue;
-
-import android.app.UiAutomation;
-import android.content.pm.PackageManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class PanicPressBackTest {
- static final String TAG = "PanicPressBackTest";
-
- @Rule
- public ActivityTestRule<PanicPressBackActivity> mActivityRule =
- new ActivityTestRule<>(PanicPressBackActivity.class);
-
- private static final int PANIC_PRESS_COUNT = 4;
- private PanicPressBackActivity mActivity;
-
- @Before
- public void setUp() {
- mActivity = mActivityRule.getActivity();
- }
-
- /**
- * Tests to ensure that the foregrounded app does not handle back button panic press on
- * non-watch devices
- */
- @Test
- public void testNonWatchBackPanicDoesNothing() throws Exception {
- // Only run for non-watch devices
- if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- return;
- }
-
- final UiAutomation automation = InstrumentationRegistry.getInstrumentation()
- .getUiAutomation();
-
- // Press back button PANIC_PRESS_COUNT times
- long startTime = System.currentTimeMillis();
- for (int i = 0; i < PANIC_PRESS_COUNT; ++i) {
- long currentTime = startTime + i;
- automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_BACK, 0), true);
- automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_UP,
- KeyEvent.KEYCODE_BACK, 0), true);
- }
-
- // Wait multi press time out plus some time to give the system time to respond
- long timeoutMs = ViewConfiguration.getMultiPressTimeout() + TimeUnit.SECONDS.toMillis(1);
-
- // Assert activity was not stopped, indicating panic press was not able to exit the app
- assertFalse(mActivity.mWaitForPanicBackLatch.await(timeoutMs, TimeUnit.MILLISECONDS));
- }
-
- /**
- * Tests to ensure that the foregrounded app does handle back button panic press on watch
- * devices
- */
- @Test
- public void testWatchBackPanicReceivesHomeRequest() throws Exception {
- // Only run for watch devices
- if (!mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- return;
- }
-
- final UiAutomation automation = InstrumentationRegistry.getInstrumentation()
- .getUiAutomation();
-
- // Press back button PANIC_PRESS_COUNT times
- long startTime = System.currentTimeMillis();
- for (int i = 0; i < PANIC_PRESS_COUNT; ++i) {
- // Assert activity hasn't stopped yet
- assertFalse(mActivity.mWaitForPanicBackLatch.await(0, TimeUnit.MILLISECONDS));
- long currentTime = startTime + i;
- automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_BACK, 0), true);
- automation.injectInputEvent(new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_UP,
- KeyEvent.KEYCODE_BACK, 0), true);
- }
-
- // Wait multi press time out plus some time to give the system time to respond
- long timeoutMs = ViewConfiguration.getMultiPressTimeout() + TimeUnit.SECONDS.toMillis(1);
-
- // Assert activity was stopped, indicating that panic press was able to exit the app
- assertTrue(mActivity.mWaitForPanicBackLatch.await(timeoutMs, TimeUnit.MILLISECONDS));
- }
-}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/TooltipTest.java b/tests/tests/view/src/android/view/cts/TooltipTest.java
index 1717763..90ab139 100644
--- a/tests/tests/view/src/android/view/cts/TooltipTest.java
+++ b/tests/tests/view/src/android/view/cts/TooltipTest.java
@@ -182,9 +182,13 @@
mInstrumentation.sendPointerSync(event);
}
+ private void injectHoverMove(int source, View target, int offsetX, int offsetY) {
+ injectMotionEvent(obtainMotionEvent(
+ source, target, MotionEvent.ACTION_HOVER_MOVE, offsetX, offsetY));
+ }
+
private void injectHoverMove(View target, int offsetX, int offsetY) {
- injectMotionEvent(obtainMouseEvent(
- target, MotionEvent.ACTION_HOVER_MOVE, offsetX, offsetY));
+ injectHoverMove(InputDevice.SOURCE_MOUSE, target, offsetX, offsetY);
}
private void injectHoverMove(View target) {
@@ -197,13 +201,18 @@
}
private static MotionEvent obtainMouseEvent(View target, int action, int offsetX, int offsetY) {
+ return obtainMotionEvent(InputDevice.SOURCE_MOUSE, target, action, offsetX, offsetY);
+ }
+
+ private static MotionEvent obtainMotionEvent(
+ int source, View target, int action, int offsetX, int offsetY) {
final long eventTime = SystemClock.uptimeMillis();
final int[] xy = new int[2];
target.getLocationOnScreen(xy);
MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action,
xy[0] + target.getWidth() / 2 + offsetX, xy[1] + target.getHeight() / 2 + offsetY,
0);
- event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setSource(source);
return event;
}
@@ -250,17 +259,16 @@
}
@Test
- public void testNoTooltipOnDisabledView() throws Throwable {
+ public void testTooltipOnDisabledView() throws Throwable {
mActivityRule.runOnUiThread(() -> mTooltipView.setEnabled(false));
+ // Long click has no effect on a disabled view.
injectLongClick(mTooltipView);
assertFalse(hasTooltip(mTooltipView));
- injectLongEnter(mTooltipView);
- assertFalse(hasTooltip(mTooltipView));
-
+ // Hover does show the tooltip on a disabled view.
injectLongHoverMove(mTooltipView);
- assertFalse(hasTooltip(mTooltipView));
+ assertTrue(hasTooltip(mTooltipView));
}
@Test
@@ -656,7 +664,8 @@
injectHoverMove(mTooltipView);
mActivityRule.runOnUiThread(() -> mTooltipView.setEnabled(false));
waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
- assertFalse(hasTooltip(mTooltipView));
+ // Disabled view still displays a hover tooltip.
+ assertTrue(hasTooltip(mTooltipView));
}
@Test
@@ -787,6 +796,76 @@
}
@Test
+ public void testMouseHoverWithJitter() throws Throwable {
+ testHoverWithJitter(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ public void testStylusHoverWithJitter() throws Throwable {
+ testHoverWithJitter(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ public void testTouchscreenHoverWithJitter() throws Throwable {
+ testHoverWithJitter(InputDevice.SOURCE_TOUCHSCREEN);
+ }
+
+ private void testHoverWithJitter(int source) {
+ final int hoverSlop = ViewConfiguration.get(mTooltipView.getContext()).getScaledHoverSlop();
+ if (hoverSlop == 0) {
+ // Zero hoverSlop makes this test redundant.
+ return;
+ }
+
+ final int tooltipTimeout = ViewConfiguration.getHoverTooltipShowTimeout();
+ final long halfTimeout = tooltipTimeout / 2;
+ assertTrue(halfTimeout + WAIT_MARGIN < tooltipTimeout);
+
+ // Imitate strong jitter (above hoverSlop threshold). No tooltip should be shown.
+ int jitterHigh = hoverSlop + 1;
+ assertTrue(jitterHigh <= mTooltipView.getWidth());
+ assertTrue(jitterHigh <= mTooltipView.getHeight());
+
+ injectHoverMove(source, mTooltipView, 0, 0);
+ waitOut(halfTimeout);
+ assertFalse(hasTooltip(mTooltipView));
+
+ injectHoverMove(source, mTooltipView, jitterHigh, 0);
+ waitOut(halfTimeout);
+ assertFalse(hasTooltip(mTooltipView));
+
+ injectHoverMove(source, mTooltipView, 0, 0);
+ waitOut(halfTimeout);
+ assertFalse(hasTooltip(mTooltipView));
+
+ injectHoverMove(source, mTooltipView, 0, jitterHigh);
+ waitOut(halfTimeout);
+ assertFalse(hasTooltip(mTooltipView));
+
+ // Jitter below threshold should be ignored and the tooltip should be shown.
+ injectHoverMove(source, mTooltipView, 0, 0);
+ waitOut(halfTimeout);
+ assertFalse(hasTooltip(mTooltipView));
+
+ int jitterLow = hoverSlop - 1;
+ injectHoverMove(source, mTooltipView, jitterLow, 0);
+ waitOut(halfTimeout);
+ assertTrue(hasTooltip(mTooltipView));
+
+ // Dismiss the tooltip
+ injectShortClick(mTooltipView);
+ assertFalse(hasTooltip(mTooltipView));
+
+ injectHoverMove(source, mTooltipView, 0, 0);
+ waitOut(halfTimeout);
+ assertFalse(hasTooltip(mTooltipView));
+
+ injectHoverMove(source, mTooltipView, 0, jitterLow);
+ waitOut(halfTimeout);
+ assertTrue(hasTooltip(mTooltipView));
+ }
+
+ @Test
public void testTooltipInPopup() throws Throwable {
TextView popupContent = new TextView(mActivity);
diff --git a/tests/tests/view/src/android/view/cts/TouchDelegateTest.java b/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
index 1fd3d8c..8def4b0 100644
--- a/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
+++ b/tests/tests/view/src/android/view/cts/TouchDelegateTest.java
@@ -16,24 +16,19 @@
package android.view.cts;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
-import android.app.Activity;
import android.app.Instrumentation;
+import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
+
import org.junit.Before;
import org.junit.Rule;
@@ -44,37 +39,89 @@
@RunWith(AndroidJUnit4.class)
public class TouchDelegateTest {
private Instrumentation mInstrumentation;
- private Activity mActivity;
- private Button mButton;
+ private TouchDelegateTestActivity mActivity;
@Rule
- public ActivityTestRule<MockActivity> mActivityRule =
- new ActivityTestRule<>(MockActivity.class);
+ public ActivityTestRule<TouchDelegateTestActivity> mActivityRule =
+ new ActivityTestRule<>(TouchDelegateTestActivity.class);
@Before
public void setup() throws Throwable {
mActivity = mActivityRule.getActivity();
+ mActivity.resetCounters();
mInstrumentation = InstrumentationRegistry.getInstrumentation();
-
- mButton = new Button(mActivity);
- mActivityRule.runOnUiThread(() -> mActivity.addContentView(
- mButton, new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)));
mInstrumentation.waitForIdleSync();
}
- @UiThreadTest
@Test
- public void testOnTouchEvent() {
- // test callback of onTouchEvent
- final View view = new View(mActivity);
- final TouchDelegate touchDelegate = mock(TouchDelegate.class);
- view.setTouchDelegate(touchDelegate);
+ public void testParentClick() {
+ // If only clicking parent, button should not receive click
+ clickParent();
+ assertEquals(0, mActivity.getButtonClickCount());
+ assertEquals(1, mActivity.getParentClickCount());
- final int xInside = (mButton.getLeft() + mButton.getRight()) / 3;
- final int yInside = (mButton.getTop() + mButton.getBottom()) / 3;
+ // When clicking TouchDelegate area, both parent and button
+ // should receive DOWN and UP events. However, click will only be generated for the button
+ mActivity.resetCounters();
+ clickTouchDelegateArea();
+ assertEquals(1, mActivity.getButtonClickCount());
+ assertEquals(0, mActivity.getParentClickCount());
- view.onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, xInside, yInside, 0));
- verify(touchDelegate, times(1)).onTouchEvent(any(MotionEvent.class));
+ // Ensure parent can still receive clicks after TouchDelegate has been activated once
+ mActivity.resetCounters();
+ clickParent();
+ assertEquals(0, mActivity.getButtonClickCount());
+ assertEquals(1, mActivity.getParentClickCount());
+ }
+
+ @Test
+ public void testCancelEvent() {
+ // Ensure events with ACTION_CANCEL are received by the TouchDelegate
+ final long downTime = SystemClock.uptimeMillis();
+ dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, mActivity.touchDelegateY,
+ downTime);
+ dispatchMotionEventToActivity(MotionEvent.ACTION_CANCEL, mActivity.touchDelegateY,
+ downTime);
+ mInstrumentation.waitForIdleSync();
+
+ MotionEvent event = mActivity.removeOldestButtonEvent();
+ assertNotNull(event);
+ assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
+ event.recycle();
+ event = mActivity.removeOldestButtonEvent();
+ assertNotNull(event);
+ assertEquals(MotionEvent.ACTION_CANCEL, event.getAction());
+ event.recycle();
+ assertNull(mActivity.removeOldestButtonEvent());
+ assertEquals(0, mActivity.getButtonClickCount());
+ assertEquals(0, mActivity.getParentClickCount());
+ }
+
+ private void clickParent() {
+ click(mActivity.parentViewY);
+ }
+
+ private void clickTouchDelegateArea() {
+ click(mActivity.touchDelegateY);
+ }
+
+ // Low-level input-handling functions for the activity
+
+ private void click(int y) {
+ final long downTime = SystemClock.uptimeMillis();
+ dispatchMotionEventToActivity(MotionEvent.ACTION_DOWN, y, downTime);
+ dispatchMotionEventToActivity(MotionEvent.ACTION_UP, y, downTime);
+ mInstrumentation.waitForIdleSync();
+ }
+
+ private void dispatchMotionEventToActivity(int action, int y, long downTime) {
+ mActivity.runOnUiThread(() -> {
+ final long eventTime = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
+ mActivity.x, y, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ mActivity.dispatchTouchEvent(event);
+ event.recycle();
+ });
}
}
diff --git a/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java b/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
new file mode 100644
index 0000000..0f01a95
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/TouchDelegateTestActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * The layout is:
+ * | <-- top edge of RelativeLayout (parent)
+ * |
+ * |
+ * | (touches in this region should go to parent only)
+ * |
+ * |
+ * | <-- TouchDelegate boundary
+ * |
+ * |
+ * | (touches in this region should go to button + parent)
+ * |
+ * |
+ * | <-- Button top boundary
+ * |
+ */
+public class TouchDelegateTestActivity extends Activity {
+ // Counters for click events. Must use waitForIdleSync() before accessing these
+ private volatile int mParentClickCount = 0;
+ private volatile int mButtonClickCount = 0;
+ // Storage for MotionEvents received by the TouchDelegate
+ private Queue<MotionEvent> mButtonEvents = new ArrayDeque<>();
+
+ // Coordinates for injecting input events from the test
+ public int x; // common X coordinate for all input events - center of the screen
+ public int touchDelegateY; // Y coordinate for touches inside TouchDelegate area
+ public int parentViewY; // Y coordinate for touches inside parent area only
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.touch_delegate_test_activity_layout);
+
+ final Button button = findViewById(R.id.button);
+ final View parent = findViewById(R.id.layout);
+
+ parent.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final int[] parentLocation = new int[2];
+ parent.getLocationOnScreen(parentLocation);
+ final int[] buttonLocation = new int[2];
+ button.getLocationOnScreen(buttonLocation);
+ x = parentLocation[0] + parent.getWidth() / 2;
+ final int gap = buttonLocation[1] - parentLocation[1];
+
+ Rect rect = new Rect();
+ button.getHitRect(rect);
+ rect.top -= gap / 2; // TouchDelegate is halfway between button and parent
+ parent.setTouchDelegate(new TouchDelegate(rect, button));
+ touchDelegateY = buttonLocation[1] - gap / 4;
+ parentViewY = parentLocation[1] + gap / 4;
+ }
+ });
+
+ parent.setOnClickListener(v -> mParentClickCount++);
+ button.setOnClickListener(v -> mButtonClickCount++);
+ button.setOnTouchListener((v, event) -> {
+ mButtonEvents.add(MotionEvent.obtain(event));
+ return TouchDelegateTestActivity.super.onTouchEvent(event);
+ });
+ }
+
+ void resetCounters() {
+ mParentClickCount = 0;
+ mButtonClickCount = 0;
+ mButtonEvents.clear();
+ }
+
+ int getButtonClickCount() {
+ return mButtonClickCount;
+ }
+
+ int getParentClickCount() {
+ return mParentClickCount;
+ }
+
+ /**
+ * Remove and return the oldest MotionEvent. Caller must recycle the returned object.
+ * @return the oldest MotionEvent that the Button has received
+ */
+ MotionEvent removeOldestButtonEvent() {
+ return mButtonEvents.poll();
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java b/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
index 774aadb..955cf7c 100644
--- a/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
+++ b/tests/tests/view/src/android/view/cts/VelocityTrackerTest.java
@@ -174,7 +174,7 @@
}
private void addMovement() {
- if (mTime >= mLastTime) {
+ if (mTime > mLastTime) {
MotionEvent ev = MotionEvent.obtain(0L, mTime, MotionEvent.ACTION_MOVE, mPx, mPy, 0);
mVelocityTracker.addMovement(ev);
ev.recycle();
@@ -201,9 +201,9 @@
if (errorVx > tolerance || errorVy > tolerance) {
fail(String.format("Velocity exceeds tolerance of %6.1f%%: "
+ "expected vx=%6.1f, vy=%6.1f. "
- + "actual vx=%6.1f (%6.1f%%), vy=%6.1f (%6.1f%%)",
+ + "actual vx=%6.1f (%6.1f%%), vy=%6.1f (%6.1f%%). %s",
tolerance * 100.0f, mVx, mVy,
- estimatedVx, errorVx * 100.0f, estimatedVy, errorVy * 100.0f));
+ estimatedVx, errorVx * 100.0f, estimatedVy, errorVy * 100.0f, message));
}
}
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 831aa15..7c05d26 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -99,6 +99,7 @@
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.compatibility.common.util.CtsMouseUtil;
import com.android.compatibility.common.util.CtsTouchUtils;
@@ -133,6 +134,7 @@
private ViewTestCtsActivity mActivity;
private Resources mResources;
private MockViewParent mMockParent;
+ private Context mContext;
@Rule
public ActivityTestRule<ViewTestCtsActivity> mActivityRule =
@@ -145,11 +147,13 @@
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getTargetContext();
mActivity = mActivityRule.getActivity();
PollingCheck.waitFor(mActivity::hasWindowFocus);
mResources = mActivity.getResources();
mMockParent = new MockViewParent(mActivity);
- assertTrue(mActivity.waitForWindowFocus(5 * DateUtils.SECOND_IN_MILLIS));
+ PollingCheck.waitFor(5 * DateUtils.SECOND_IN_MILLIS, mActivity::hasWindowFocus);
+ assertTrue(mActivity.hasWindowFocus());
}
@Test
@@ -500,6 +504,31 @@
Bitmap bitmap = BitmapFactory.decodeResource(mResources, R.drawable.icon_blue);
assertNotNull(PointerIcon.create(bitmap, 0, 0));
+ assertNotNull(PointerIcon.create(bitmap, bitmap.getWidth() / 2, bitmap.getHeight() / 2));
+
+ try {
+ PointerIcon.create(bitmap, -1, 0);
+ fail("Hotspot x can not be < 0");
+ } catch (IllegalArgumentException ignore) {
+ }
+
+ try {
+ PointerIcon.create(bitmap, 0, -1);
+ fail("Hotspot y can not be < 0");
+ } catch (IllegalArgumentException ignore) {
+ }
+
+ try {
+ PointerIcon.create(bitmap, bitmap.getWidth(), 0);
+ fail("Hotspot x cannot be >= width");
+ } catch (IllegalArgumentException ignore) {
+ }
+
+ try {
+ PointerIcon.create(bitmap, 0, bitmap.getHeight());
+ fail("Hotspot x cannot be >= height");
+ } catch (IllegalArgumentException e) {
+ }
}
private void assertSystemPointerIcon(int style) {
@@ -2385,6 +2414,109 @@
}
@Test
+ public void testKeyFallback() throws Throwable {
+ MockFallbackListener listener = new MockFallbackListener();
+ ViewGroup viewGroup = (ViewGroup) mActivity.findViewById(R.id.viewlayout_root);
+ // Attaching a fallback handler
+ TextView mockView1 = new TextView(mActivity);
+ mockView1.addKeyFallbackListener(listener);
+
+ // Before the view is attached, it shouldn't respond to anything
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertFalse(listener.fired());
+
+ // Once attached, it should start receiving fallback events
+ mActivityRule.runOnUiThread(() -> viewGroup.addView(mockView1));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertTrue(listener.fired());
+ listener.reset();
+
+ // If multiple on one view, last added should receive event first
+ MockFallbackListener listener2 = new MockFallbackListener();
+ listener2.mReturnVal = true;
+ mActivityRule.runOnUiThread(() -> mockView1.addKeyFallbackListener(listener2));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertTrue(listener2.fired());
+ assertFalse(listener.fired());
+ listener2.reset();
+
+ // If removed, it should not receive fallbacks anymore
+ mActivityRule.runOnUiThread(() -> {
+ mockView1.removeKeyFallbackListener(listener);
+ mockView1.removeKeyFallbackListener(listener2);
+ });
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertFalse(listener.fired());
+
+ mActivityRule.runOnUiThread(() -> mActivity.setContentView(R.layout.key_fallback_layout));
+ mInstrumentation.waitForIdleSync();
+ View higherInNormal = mActivity.findViewById(R.id.higher_in_normal);
+ View higherGroup = mActivity.findViewById(R.id.higher_group);
+ View lowerInHigher = mActivity.findViewById(R.id.lower_in_higher);
+ View lastButton = mActivity.findViewById(R.id.last_button);
+ View lastInHigher = mActivity.findViewById(R.id.last_in_higher);
+ View lastInNormal = mActivity.findViewById(R.id.last_in_normal);
+
+ View[] allViews = new View[]{higherInNormal, higherGroup, lowerInHigher, lastButton,
+ lastInHigher, lastInNormal};
+
+ // Test ordering by depth
+ listener.mReturnVal = true;
+ mActivityRule.runOnUiThread(() -> {
+ for (View v : allViews) {
+ v.addKeyFallbackListener(listener);
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertEquals(lastInHigher, listener.mLastView);
+ listener.reset();
+
+ mActivityRule.runOnUiThread(() -> lastInHigher.removeKeyFallbackListener(listener));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertEquals(lowerInHigher, listener.mLastView);
+ listener.reset();
+
+ mActivityRule.runOnUiThread(() -> lowerInHigher.removeKeyFallbackListener(listener));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertEquals(higherGroup, listener.mLastView);
+ listener.reset();
+
+ mActivityRule.runOnUiThread(() -> higherGroup.removeKeyFallbackListener(listener));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertEquals(lastButton, listener.mLastView);
+ listener.reset();
+
+ mActivityRule.runOnUiThread(() -> lastButton.removeKeyFallbackListener(listener));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertEquals(higherInNormal, listener.mLastView);
+ listener.reset();
+
+ mActivityRule.runOnUiThread(() -> higherInNormal.removeKeyFallbackListener(listener));
+ mInstrumentation.waitForIdleSync();
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertEquals(lastInNormal, listener.mLastView);
+ listener.reset();
+
+ // Test "capture"
+ mActivityRule.runOnUiThread(() -> lastInNormal.requestFocus());
+ mInstrumentation.waitForIdleSync();
+ lastInNormal.setOnKeyListener((v, keyCode, event)
+ -> (keyCode == KeyEvent.KEYCODE_B && event.getAction() == KeyEvent.ACTION_UP));
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_B);
+ assertTrue(listener.fired()); // checks that both up and down were received
+ listener.reset();
+ }
+
+ @Test
public void testWindowVisibilityChanged() throws Throwable {
final MockView mockView = new MockView(mActivity);
final ViewGroup viewGroup = (ViewGroup) mActivity.findViewById(R.id.viewlayout_root);
@@ -3884,7 +4016,9 @@
final MockEditText editText = new MockEditText(mActivity);
mActivityRule.runOnUiThread(() -> {
- viewGroup.addView(editText);
+ // Give a fixed size since, on most devices, the edittext is off-screen
+ // and therefore doesn't get laid-out properly.
+ viewGroup.addView(editText, 100, 30);
editText.requestFocus();
});
mInstrumentation.waitForIdleSync();
@@ -4340,6 +4474,42 @@
event.recycle();
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testScaleXNaN() {
+ View view = new View(mContext);
+ view.setScaleX(Float.NaN);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testScaleXPositiveInfinity() {
+ View view = new View(mContext);
+ view.setScaleX(Float.POSITIVE_INFINITY);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testScaleXNegativeInfinity() {
+ View view = new View(mContext);
+ view.setScaleX(Float.NEGATIVE_INFINITY);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testScaleYNaN() {
+ View view = new View(mContext);
+ view.setScaleY(Float.NaN);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testScaleYPositiveInfinity() {
+ View view = new View(mContext);
+ view.setScaleY(Float.POSITIVE_INFINITY);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testScaleYNegativeInfinity() {
+ View view = new View(mContext);
+ view.setScaleY(Float.NEGATIVE_INFINITY);
+ }
+
private static class MockDrawable extends Drawable {
private boolean mCalledSetTint = false;
@@ -4691,6 +4861,29 @@
public View firstChild;
}
+ private static class MockFallbackListener implements View.OnKeyFallbackListener {
+ public View mLastView = null;
+ public boolean mGotUp = false;
+ public boolean mReturnVal = false;
+
+ @Override
+ public boolean onKeyFallback(View v, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mLastView = v;
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ mGotUp = true;
+ }
+ return mReturnVal;
+ }
+ public void reset() {
+ mLastView = null;
+ mGotUp = false;
+ }
+ public boolean fired() {
+ return mLastView != null && mGotUp;
+ }
+ }
+
private static final Class<?> ASYNC_INFLATE_VIEWS[] = {
android.app.FragmentBreadCrumbs.class,
// DISABLED because it doesn't have a AppWidgetHostView(Context, AttributeSet)
diff --git a/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java b/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java
index 9766fa2..9117925 100644
--- a/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java
+++ b/tests/tests/view/src/android/view/cts/ViewTestCtsActivity.java
@@ -18,52 +18,13 @@
import android.app.Activity;
import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
import android.view.cts.R;
public class ViewTestCtsActivity extends Activity {
- private boolean mHasWindowFocus = false;
- private final Object mHasWindowFocusLock = new Object();
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.view_layout);
}
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- if (!hasFocus) {
- Log.w("ViewTestCtsActivity", "ViewTestCtsActivity lost window focus");
- }
- synchronized(mHasWindowFocusLock) {
- mHasWindowFocus = hasFocus;
- mHasWindowFocusLock.notify();
- }
- }
-
- /**
- * Blocks the calling thread until the {@link ViewTestCtsActivity} has window focus or the
- * specified duration (in milliseconds) has passed.
- */
- public boolean waitForWindowFocus(long durationMillis) {
- long elapsedMillis = SystemClock.elapsedRealtime();
- synchronized(mHasWindowFocusLock) {
- mHasWindowFocus = hasWindowFocus();
- while (!mHasWindowFocus && durationMillis > 0) {
- long newElapsedMillis = SystemClock.elapsedRealtime();
- durationMillis -= (newElapsedMillis - elapsedMillis);
- elapsedMillis = newElapsedMillis;
- if (durationMillis > 0) {
- try {
- mHasWindowFocusLock.wait(durationMillis);
- } catch (InterruptedException e) {
- }
- }
- }
- return mHasWindowFocus;
- }
- }
}
diff --git a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
index 21fdd89..d3d600a 100644
--- a/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
+++ b/tests/tests/view/src/android/view/cts/View_FocusHandlingTest.java
@@ -227,6 +227,175 @@
@UiThreadTest
@Test
+ public void testEnabledHandling() {
+ Activity activity = mActivityRule.getActivity();
+
+ View v1 = activity.findViewById(R.id.view1);
+ View v2 = activity.findViewById(R.id.view2);
+ View v3 = activity.findViewById(R.id.view3);
+ View v4 = activity.findViewById(R.id.view4);
+
+ for (View v : new View[]{v1, v2, v3, v4}) v.setFocusable(true);
+
+ assertTrue(v1.requestFocus());
+
+ // disabled view should not be focusable
+ assertTrue(v1.hasFocus());
+ v1.setEnabled(false);
+ assertFalse(v1.hasFocus());
+ v1.requestFocus();
+ assertFalse(v1.hasFocus());
+ v1.setEnabled(true);
+ v1.requestFocus();
+ assertTrue(v1.hasFocus());
+
+ // an enabled view should not take focus if not visible OR not enabled
+ v1.setEnabled(false);
+ v1.setVisibility(View.INVISIBLE);
+ assertFalse(v1.hasFocus());
+ v1.setEnabled(true);
+ v1.requestFocus();
+ assertFalse(v1.hasFocus());
+ v1.setEnabled(false);
+ v1.setVisibility(View.VISIBLE);
+ v1.requestFocus();
+ assertFalse(v1.hasFocus());
+ v1.setEnabled(true);
+ v1.requestFocus();
+ assertTrue(v1.hasFocus());
+
+ // test hasFocusable
+ ViewGroup parent = (ViewGroup) v1.getParent();
+ assertTrue(parent.hasFocusable());
+ for (View v : new View[]{v1, v2, v3, v4}) v.setEnabled(false);
+ assertFalse(v1.isFocused());
+ assertFalse(v2.isFocused());
+ assertFalse(v3.isFocused());
+ assertFalse(v4.isFocused());
+ assertFalse(parent.hasFocusable());
+
+ // a view enabled while nothing has focus should get focus.
+ for (View v : new View[]{v1, v2, v3, v4}) v.setEnabled(true);
+ assertEquals(true, v1.isFocused());
+ }
+
+ @Test
+ public void testSizeHandling() {
+ Activity activity = mActivityRule.getActivity();
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ View v5 = new Button(activity);
+ ViewGroup layout = activity.findViewById(R.id.auto_test_area);
+
+ // Test requestFocus before first layout focuses if non-0 size
+ activity.runOnUiThread(() -> {
+ layout.addView(v5, 30, 30);
+ assertTrue(isZeroSize(v5));
+ assertTrue(v5.requestFocus());
+ });
+ instrumentation.waitForIdleSync();
+ assertFalse(isZeroSize(v5));
+ assertTrue(v5.isFocused());
+
+ // Test resize to 0 defocuses
+ activity.runOnUiThread(() -> {
+ v5.setRight(v5.getLeft());
+ assertEquals(0, v5.getWidth());
+ });
+ instrumentation.waitForIdleSync();
+ assertTrue(isZeroSize(v5));
+ assertFalse(v5.isFocused());
+
+ // Test requestFocus on laid-out 0-size fails
+ activity.runOnUiThread(() -> assertFalse(v5.requestFocus()));
+
+ activity.runOnUiThread(() -> layout.removeAllViews());
+
+ // Test requestFocus before first layout focuses a child if non-0 size
+ LinearLayout ll0 = new LinearLayout(activity);
+ ll0.setFocusable(true);
+ ll0.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ View butInScroll = new Button(activity);
+ activity.runOnUiThread(() -> {
+ ll0.addView(butInScroll, 40, 40);
+ layout.addView(ll0, 100, 100);
+ assertTrue(isZeroSize(butInScroll));
+ assertTrue(ll0.requestFocus());
+ });
+ instrumentation.waitForIdleSync();
+ assertFalse(isZeroSize(butInScroll));
+ assertTrue(butInScroll.isFocused());
+
+ // Test focusableViewAvailable on resize to non-0 size
+ activity.runOnUiThread(() -> {
+ butInScroll.setRight(v5.getLeft());
+ });
+ instrumentation.waitForIdleSync();
+ assertTrue(isZeroSize(butInScroll));
+ assertTrue(ll0.isFocused());
+
+ activity.runOnUiThread(() -> layout.removeAllViews());
+ instrumentation.waitForIdleSync();
+
+ // Test requestFocus before first layout defocuses if still 0 size
+ LinearLayout ll = new LinearLayout(activity);
+ View zeroSizeBut = new Button(activity);
+ activity.runOnUiThread(() -> {
+ ll.addView(zeroSizeBut, 30, 0);
+ layout.addView(ll, 100, 100);
+ assertTrue(zeroSizeBut.requestFocus());
+ });
+ instrumentation.waitForIdleSync();
+ assertTrue(isZeroSize(zeroSizeBut));
+ assertFalse(zeroSizeBut.isFocused());
+
+ activity.runOnUiThread(() -> layout.removeAllViews());
+ instrumentation.waitForIdleSync();
+
+ // Test requestFocus before first layout focuses parent if child is still 0 size
+ LinearLayout ll2 = new LinearLayout(activity);
+ ll2.setFocusable(true);
+ ll2.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ View zeroButInAfter = new Button(activity);
+ activity.runOnUiThread(() -> {
+ ll2.addView(zeroButInAfter, 40, 0);
+ layout.addView(ll2, 100, 100);
+ assertTrue(ll2.requestFocus());
+ assertTrue(zeroButInAfter.isFocused());
+ });
+ instrumentation.waitForIdleSync();
+ assertTrue(isZeroSize(zeroButInAfter));
+ assertFalse(zeroButInAfter.isFocused());
+ assertTrue(ll2.isFocused());
+
+ activity.runOnUiThread(() -> layout.removeAllViews());
+ instrumentation.waitForIdleSync();
+
+ // Test that we don't focus anything above/outside of where we requested focus
+ LinearLayout ll3 = new LinearLayout(activity);
+ Button outside = new Button(activity);
+ LinearLayout sub = new LinearLayout(activity);
+ Button inside = new Button(activity);
+ activity.runOnUiThread(() -> {
+ ll3.addView(outside, 40, 40);
+ sub.addView(inside, 30, 0);
+ ll3.addView(sub, 40, 40);
+ layout.addView(ll3, 100, 100);
+ assertTrue(sub.requestFocus());
+ assertTrue(inside.isFocused());
+ });
+ instrumentation.waitForIdleSync();
+ assertTrue(isZeroSize(inside));
+ assertTrue(outside.isFocusable() && !isZeroSize(outside));
+ assertNull(layout.getRootView().findFocus());
+ }
+
+ private boolean isZeroSize(View view) {
+ return view.getWidth() <= 0 || view.getHeight() <= 0;
+ }
+
+ @UiThreadTest
+ @Test
public void testFocusAuto() {
Activity activity = mActivityRule.getActivity();
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index 71ca678..f066898 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -29,6 +29,7 @@
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextSelection;
import org.junit.Before;
@@ -42,7 +43,10 @@
private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
private static final int START = 1;
private static final int END = 3;
- private static final String TEXT = "text";
+ // This text has lots of things that are probably entities in many cases.
+ private static final String TEXT = "An email address is test@example.com. A phone number"
+ + " might be +12122537077. Somebody lives at 123 Main Street, Mountain View, CA,"
+ + " and there's good stuff at https://www.android.com :)";
private TextClassificationManager mManager;
private TextClassifier mClassifier;
@@ -57,12 +61,14 @@
@Test
public void testSmartSelection() {
- assertValidResult(mClassifier.suggestSelection(TEXT, START, END, LOCALES));
+ assertValidResult(mClassifier.suggestSelection(TEXT, START, END,
+ new TextSelection.Options().setDefaultLocales(LOCALES)));
}
@Test
public void testClassifyText() {
- assertValidResult(mClassifier.classifyText(TEXT, START, END, LOCALES));
+ assertValidResult(mClassifier.classifyText(TEXT, START, END,
+ new TextClassification.Options().setDefaultLocales(LOCALES)));
}
@Test
@@ -70,14 +76,16 @@
mManager.setTextClassifier(TextClassifier.NO_OP);
mClassifier = mManager.getTextClassifier();
- final TextSelection selection = mClassifier.suggestSelection(TEXT, START, END, LOCALES);
+ final TextSelection selection = mClassifier.suggestSelection(TEXT, START, END,
+ new TextSelection.Options().setDefaultLocales(LOCALES));
assertValidResult(selection);
assertEquals(START, selection.getSelectionStartIndex());
assertEquals(END, selection.getSelectionEndIndex());
assertEquals(0, selection.getEntityCount());
final TextClassification classification =
- mClassifier.classifyText(TEXT, START, END, LOCALES);
+ mClassifier.classifyText(TEXT, START, END,
+ new TextClassification.Options().setDefaultLocales(LOCALES));
assertValidResult(classification);
assertNull(classification.getText());
assertEquals(0, classification.getEntityCount());
@@ -88,6 +96,11 @@
}
@Test
+ public void testGenerateLinks() {
+ assertValidResult(mClassifier.generateLinks(TEXT, null));
+ }
+
+ @Test
public void testSetTextClassifier() {
final TextClassifier classifier = mock(TextClassifier.class);
mManager.setTextClassifier(classifier);
@@ -96,6 +109,8 @@
private static void assertValidResult(TextSelection selection) {
assertNotNull(selection);
+ assertTrue(selection.getSelectionStartIndex() >= 0);
+ assertTrue(selection.getSelectionEndIndex() > selection.getSelectionStartIndex());
assertTrue(selection.getEntityCount() >= 0);
for (int i = 0; i < selection.getEntityCount(); i++) {
final String entity = selection.getEntity(i);
@@ -104,6 +119,7 @@
assertTrue(confidenceScore >= 0);
assertTrue(confidenceScore <= 1);
}
+ assertNotNull(selection.getSignature());
}
private static void assertValidResult(TextClassification classification) {
@@ -116,6 +132,23 @@
assertTrue(confidenceScore >= 0);
assertTrue(confidenceScore <= 1);
}
+ assertTrue(classification.getSecondaryActionsCount() >= 0);
+ assertNotNull(classification.getSignature());
+ }
+
+ private static void assertValidResult(TextLinks links) {
+ assertNotNull(links);
+ for (TextLinks.TextLink link : links.getLinks()) {
+ assertTrue(link.getEntityCount() > 0);
+ assertTrue(link.getStart() >= 0);
+ assertTrue(link.getStart() <= link.getEnd());
+ for (int i = 0; i < link.getEntityCount(); i++) {
+ String entityType = link.getEntity(i);
+ assertNotNull(entityType);
+ final float confidenceScore = link.getConfidenceScore(entityType);
+ assertTrue(confidenceScore >= 0);
+ assertTrue(confidenceScore <= 1);
+ }
+ }
}
}
-
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
new file mode 100644
index 0000000..6af8378
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.LocaleList;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextSelection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * TextClassifier value objects tests.
+ *
+ * <p>Contains unit tests for value objects passed to/from TextClassifier APIs.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassifierValueObjectsTest {
+
+ private static final double ACCEPTED_DELTA = 0.0000001;
+ private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+ private static final int START = 5;
+ private static final int END = 20;
+ private static final String SIGNATURE = "sig.na-ture";
+ private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+ @Test
+ public void testTextSelection() {
+ final float addressScore = 0.1f;
+ final float emailScore = 0.9f;
+
+ final TextSelection selection = new TextSelection.Builder(START, END)
+ .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+ .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+ .setSignature(SIGNATURE)
+ .build();
+
+ assertEquals(START, selection.getSelectionStartIndex());
+ assertEquals(END, selection.getSelectionEndIndex());
+ assertEquals(2, selection.getEntityCount());
+ assertEquals(TextClassifier.TYPE_EMAIL, selection.getEntity(0));
+ assertEquals(TextClassifier.TYPE_ADDRESS, selection.getEntity(1));
+ assertEquals(addressScore, selection.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+ ACCEPTED_DELTA);
+ assertEquals(emailScore, selection.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+ ACCEPTED_DELTA);
+ assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+ assertEquals(SIGNATURE, selection.getSignature());
+ }
+
+ @Test
+ public void testTextSelection_differentParams() {
+ final int start = 0;
+ final int end = 1;
+ final float confidenceScore = 0.5f;
+ final String signature = "2hukwu3m3k44f1gb0";
+
+ final TextSelection selection = new TextSelection.Builder(start, end)
+ .setEntityType(TextClassifier.TYPE_URL, confidenceScore)
+ .setSignature(signature)
+ .build();
+
+ assertEquals(start, selection.getSelectionStartIndex());
+ assertEquals(end, selection.getSelectionEndIndex());
+ assertEquals(1, selection.getEntityCount());
+ assertEquals(TextClassifier.TYPE_URL, selection.getEntity(0));
+ assertEquals(confidenceScore, selection.getConfidenceScore(TextClassifier.TYPE_URL),
+ ACCEPTED_DELTA);
+ assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+ assertEquals(signature, selection.getSignature());
+ }
+
+ @Test
+ public void testTextSelection_defaultValues() {
+ TextSelection selection = new TextSelection.Builder(START, END).build();
+ assertEquals(0, selection.getEntityCount());
+ assertEquals("", selection.getSignature());
+ }
+
+ @Test
+ public void testTextSelection_prunedConfidenceScore() {
+ final float phoneScore = -0.1f;
+ final float prunedPhoneScore = 0f;
+ final float otherScore = 1.5f;
+ final float prunedOtherScore = 1.0f;
+
+ final TextSelection selection = new TextSelection.Builder(START, END)
+ .setEntityType(TextClassifier.TYPE_PHONE, phoneScore)
+ .setEntityType(TextClassifier.TYPE_OTHER, otherScore)
+ .build();
+
+ assertEquals(prunedPhoneScore, selection.getConfidenceScore(TextClassifier.TYPE_PHONE),
+ ACCEPTED_DELTA);
+ assertEquals(prunedOtherScore, selection.getConfidenceScore(TextClassifier.TYPE_OTHER),
+ ACCEPTED_DELTA);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTextSelection_invalidStartParams() {
+ new TextSelection.Builder(-1 /* start */, END)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTextSelection_invalidEndParams() {
+ new TextSelection.Builder(START, 0 /* end */)
+ .build();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testTextSelection_invalidSignature() {
+ new TextSelection.Builder(START, END)
+ .setSignature(null)
+ .build();
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void testTextSelection_entityIndexOutOfBounds() {
+ final TextSelection selection = new TextSelection.Builder(START, END).build();
+ final int outOfBoundsIndex = selection.getEntityCount();
+ selection.getEntity(outOfBoundsIndex);
+ }
+
+ @Test
+ public void testTextSelectionOptions() {
+ final TextSelection.Options options = new TextSelection.Options()
+ .setDefaultLocales(LOCALES);
+ assertEquals(LOCALES, options.getDefaultLocales());
+ }
+
+ @Test
+ public void testTextSelectionOptions_nullValues() {
+ final TextSelection.Options options = new TextSelection.Options()
+ .setDefaultLocales(null);
+ assertNull(options.getDefaultLocales());
+ }
+
+ @Test
+ public void testTextSelectionOptions_defaultValues() {
+ final TextSelection.Options options = new TextSelection.Options();
+ assertNull(options.getDefaultLocales());
+ }
+
+ @Test
+ public void testTextClassification() {
+ final float addressScore = 0.1f;
+ final float emailScore = 0.9f;
+ final Intent intent = new Intent();
+ final String label = "label";
+ final Drawable icon = new ColorDrawable(Color.RED);
+ final View.OnClickListener onClick = v -> { };
+ final Intent intent1 = new Intent();
+ final String label1 = "label1";
+ final Drawable icon1 = new ColorDrawable(Color.GREEN);
+ final View.OnClickListener onClick1 = v -> { };
+ final Intent intent2 = new Intent();
+ final String label2 = "label2";
+ final Drawable icon2 = new ColorDrawable(Color.BLUE);
+ final View.OnClickListener onClick2 = v -> { };
+
+ final TextClassification classification = new TextClassification.Builder()
+ .setText(TEXT)
+ .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+ .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+ .setPrimaryAction(intent, label, icon, onClick)
+ .addSecondaryAction(intent1, label1, icon1, onClick1)
+ .addSecondaryAction(intent2, label2, icon2, onClick2)
+ .setSignature(SIGNATURE)
+ .build();
+
+ assertEquals(TEXT, classification.getText());
+ assertEquals(2, classification.getEntityCount());
+ assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
+ assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
+ assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+ ACCEPTED_DELTA);
+ assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+ ACCEPTED_DELTA);
+ assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+
+ assertEquals(intent, classification.getIntent());
+ assertEquals(label, classification.getLabel());
+ assertEquals(icon, classification.getIcon());
+ assertEquals(onClick, classification.getOnClickListener());
+
+ assertEquals(2, classification.getSecondaryActionsCount());
+ assertEquals(intent1, classification.getSecondaryIntent(0));
+ assertEquals(label1, classification.getSecondaryLabel(0));
+ assertEquals(icon1, classification.getSecondaryIcon(0));
+ assertEquals(onClick1, classification.getSecondaryOnClickListener(0));
+ assertEquals(intent2, classification.getSecondaryIntent(1));
+ assertEquals(label2, classification.getSecondaryLabel(1));
+ assertEquals(icon2, classification.getSecondaryIcon(1));
+ assertEquals(onClick2, classification.getSecondaryOnClickListener(1));
+
+ assertEquals(SIGNATURE, classification.getSignature());
+ }
+
+ @Test
+ public void testTextClassification_defaultValues() {
+ final TextClassification classification = new TextClassification.Builder().build();
+
+ assertEquals(null, classification.getText());
+ assertEquals(0, classification.getEntityCount());
+ assertEquals(null, classification.getIntent());
+ assertEquals(null, classification.getLabel());
+ assertEquals(null, classification.getIcon());
+ assertEquals(null, classification.getOnClickListener());
+ assertEquals(0, classification.getSecondaryActionsCount());
+ assertEquals("", classification.getSignature());
+ }
+
+ @Test
+ public void testTextClassificationOptions() {
+ final TextClassification.Options options = new TextClassification.Options()
+ .setDefaultLocales(LOCALES);
+ assertEquals(LOCALES, options.getDefaultLocales());
+ }
+
+ @Test
+ public void testTextClassificationOptions_nullValues() {
+ final TextClassification.Options options = new TextClassification.Options()
+ .setDefaultLocales(null);
+ assertNull(options.getDefaultLocales());
+ }
+
+ @Test
+ public void testTextClassificationOptions_defaultValues() {
+ final TextClassification.Options options = new TextClassification.Options();
+ assertNull(options.getDefaultLocales());
+ }
+
+ // TODO: Add more tests.
+}
diff --git a/tests/tests/voiceinteraction/Android.mk b/tests/tests/voiceinteraction/Android.mk
index b83f4e9..e0708ff 100644
--- a/tests/tests/voiceinteraction/Android.mk
+++ b/tests/tests/voiceinteraction/Android.mk
@@ -23,6 +23,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := CtsVoiceInteractionCommon ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsVoiceInteractionTestCases
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
index 6ada1b8..203be4d 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -20,6 +20,7 @@
import android.app.VoiceInteractor.Prompt;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.os.AsyncTask;
import android.os.Bundle;
import android.service.voice.VoiceInteractionSession;
@@ -46,6 +47,10 @@
public void onCreate() {
super.onCreate();
Intent sessionStarted = new Intent();
+ sessionStarted.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ if (!getContext().getSystemService(LauncherApps.class).hasShortcutHostPermission()) {
+ sessionStarted.putExtra("error", "Does not have shortcut permission");
+ }
sessionStarted.setClassName("android.voiceinteraction.cts",
"android.voiceinteraction.cts.VoiceInteractionTestReceiver");
getContext().sendBroadcast(sessionStarted);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
index 07236b2..66c945a 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/LocalVoiceInteractionTest.java
@@ -71,7 +71,6 @@
if (!mHasFeature) {
return;
}
- VoiceInteractionTestReceiver.sServiceStartedLatch.await(5, TimeUnit.SECONDS);
assertTrue("Doesn't support LocalVoiceInteraction",
mTestActivity.isLocalVoiceInteractionSupported());
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
index 77c9f26..1abd00f 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
@@ -87,7 +87,7 @@
}
public void testAll() throws Exception {
- VoiceInteractionTestReceiver.sServiceStartedLatch.await(5, TimeUnit.SECONDS);
+ VoiceInteractionTestReceiver.waitSessionStarted(this, 5, TimeUnit.SECONDS);
if (!mHasFeature) {
Log.i(TAG, "The device doesn't support feature: " + FEATURE_VOICE_RECOGNIZERS);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java
index 118f049..7f875c4 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTestReceiver.java
@@ -21,15 +21,31 @@
import android.content.Intent;
import android.util.Log;
+import junit.framework.TestCase;
+
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class VoiceInteractionTestReceiver extends BroadcastReceiver {
- public static CountDownLatch sServiceStartedLatch = new CountDownLatch(1);
+ private static CountDownLatch sServiceStartedLatch = new CountDownLatch(1);
+ private static Intent sReceivedIntent;
+
+ public static void waitSessionStarted(TestCase testCase, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ if (!sServiceStartedLatch.await(5, TimeUnit.SECONDS)) {
+ testCase.fail("Timed out waiting for session to start");
+ }
+ String error = sReceivedIntent.getStringExtra("error");
+ if (error != null) {
+ testCase.fail(error);
+ }
+ }
@Override
public void onReceive(Context context, Intent intent) {
Log.i("VoiceInteractionTestReceiver", "Got broadcast that MainInteractionService started");
+ sReceivedIntent = intent;
sServiceStartedLatch.countDown();
}
}
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
index dbfb031..7c6cf53 100644
--- a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
+++ b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/TestApp.java
@@ -135,6 +135,7 @@
private void broadcastResults() {
Intent intent = new Intent(Utils.BROADCAST_INTENT);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtras(mTotalInfo);
Log.i(TAG, "broadcasting: " + intent.toString() + ", Bundle = " + mTotalInfo.toString());
sendOrderedBroadcast(intent, null, new DoneReceiver(),
diff --git a/tests/tests/voicesettings/Android.mk b/tests/tests/voicesettings/Android.mk
index cad36cb..9409073 100644
--- a/tests/tests/voicesettings/Android.mk
+++ b/tests/tests/voicesettings/Android.mk
@@ -23,6 +23,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner compatibility-device-util
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsVoiceSettingsTestCases
diff --git a/tests/tests/voicesettings/AndroidTest.xml b/tests/tests/voicesettings/AndroidTest.xml
index 421dfb7..dfb50d8 100644
--- a/tests/tests/voicesettings/AndroidTest.xml
+++ b/tests/tests/voicesettings/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Voice Settings test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/webkit/Android.mk b/tests/tests/webkit/Android.mk
index c56f00e..5612c53 100644
--- a/tests/tests/webkit/Android.mk
+++ b/tests/tests/webkit/Android.mk
@@ -21,7 +21,11 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_JAVA_LIBRARIES := android.test.runner org.apache.http.legacy
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ org.apache.http.legacy \
+ android.test.base \
+
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index 83775df..5168cda 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -22,7 +22,7 @@
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
- <application android:maxRecents="1">
+ <application android:maxRecents="1" android:usesCleartextTraffic="true">
<provider android:name="android.webkit.cts.MockContentProvider"
android:exported="true"
android:authorities="android.webkit.cts.MockContentProvider" />
@@ -56,6 +56,14 @@
</intent-filter>
</activity>
+ <service android:name="android.webkit.cts.TestProcessServiceA"
+ android:process=":testprocessA"
+ android:exported="false" />
+
+ <service android:name="android.webkit.cts.TestProcessServiceB"
+ android:process=":testprocessB"
+ android:exported="false" />
+
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="false" />
</application>
diff --git a/tests/tests/webkit/AndroidTest.xml b/tests/tests/webkit/AndroidTest.xml
index a3801e4..903aef7 100644
--- a/tests/tests/webkit/AndroidTest.xml
+++ b/tests/tests/webkit/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Webkit test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="webview" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
index ecc3e4f..0fa239d 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
@@ -101,12 +101,26 @@
// Post a string message to main frame and make sure it is received.
public void testSimpleMessageToMainFrame() throws Throwable {
+ verifyPostMessageToOrigin(Uri.parse(BASE_URI));
+ }
+
+ // Post a string message to main frame passing a wildcard as target origin
+ public void testWildcardOriginMatchesAnything() throws Throwable {
+ verifyPostMessageToOrigin(Uri.parse("*"));
+ }
+
+ // Post a string message to main frame passing an empty string as target origin
+ public void testEmptyStringOriginMatchesAnything() throws Throwable {
+ verifyPostMessageToOrigin(Uri.parse(""));
+ }
+
+ private void verifyPostMessageToOrigin(Uri origin) throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
loadPage(TITLE_FROM_POST_MESSAGE);
WebMessage message = new WebMessage(WEBVIEW_MESSAGE);
- mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
+ mOnUiThread.postWebMessage(message, origin);
waitForTitle(WEBVIEW_MESSAGE);
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
new file mode 100644
index 0000000..1854641
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+class TestProcessClient extends Assert implements AutoCloseable, ServiceConnection {
+ private Context mContext;
+
+ private static final long CONNECT_TIMEOUT_MS = 5000;
+
+ private Object mLock = new Object();
+ @GuardedBy("mLock")
+ private Messenger mService;
+ @GuardedBy("mLock")
+ private Integer mLastResult;
+ @GuardedBy("mLock")
+ private Throwable mLastException;
+
+ private final Messenger mReplyHandler = new Messenger(new ReplyHandler(Looper.getMainLooper()));
+
+ public static TestProcessClient createProcessA(Context context) throws Throwable {
+ return new TestProcessClient(context, TestProcessServiceA.class);
+ }
+
+ public static TestProcessClient createProcessB(Context context) throws Throwable {
+ return new TestProcessClient(context, TestProcessServiceB.class);
+ }
+
+ /**
+ * Subclass this to implement test code to run on the service side.
+ */
+ static abstract class TestRunnable extends Assert {
+ public abstract void run(Context ctx);
+ }
+
+ static class ProcessFreshChecker extends TestRunnable {
+ private static Object sFreshLock = new Object();
+ @GuardedBy("sFreshLock")
+ private static boolean sFreshProcess = true;
+
+ @Override
+ public void run(Context ctx) {
+ synchronized (sFreshLock) {
+ if (!sFreshProcess) {
+ fail("Service process was unexpectedly reused");
+ }
+ sFreshProcess = true;
+ }
+ }
+
+ }
+
+ private TestProcessClient(Context context, Class service) throws Throwable {
+ mContext = context;
+ Intent i = new Intent(context, service);
+ context.bindService(i, this, Context.BIND_AUTO_CREATE);
+ synchronized (mLock) {
+ if (mService == null) {
+ mLock.wait(CONNECT_TIMEOUT_MS);
+ if (mService == null) {
+ fail("Timeout waiting for connection");
+ }
+ }
+ }
+
+ // Check that we're using an actual fresh process.
+ // 1000ms timeout is plenty since the service is already running.
+ run(ProcessFreshChecker.class, 1000);
+ }
+
+ public void run(Class runnableClass, long timeoutMs) throws Throwable {
+ Message m = Message.obtain(null, TestProcessService.MSG_RUN_TEST);
+ m.replyTo = mReplyHandler;
+ m.getData().putString(TestProcessService.TEST_CLASS_KEY, runnableClass.getName());
+ int result;
+ Throwable exception;
+ synchronized (mLock) {
+ mService.send(m);
+ if (mLastResult == null) {
+ mLock.wait(timeoutMs);
+ if (mLastResult == null) {
+ fail("Timeout waiting for result");
+ }
+ }
+ result = mLastResult;
+ mLastResult = null;
+ exception = mLastException;
+ mLastException = null;
+ }
+ if (result == TestProcessService.REPLY_EXCEPTION) {
+ throw exception;
+ } else if (result != TestProcessService.REPLY_OK) {
+ fail("Unknown result from service: " + result);
+ }
+ }
+
+ public void close() {
+ synchronized (mLock) {
+ if (mService != null) {
+ try {
+ mService.send(Message.obtain(null, TestProcessService.MSG_EXIT_PROCESS));
+ } catch (RemoteException e) {}
+ mService = null;
+ mContext.unbindService(this);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ synchronized (mLock) {
+ mService = new Messenger(service);
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ synchronized (mLock) {
+ mService = null;
+ mContext.unbindService(this);
+ mLastResult = TestProcessService.REPLY_EXCEPTION;
+ mLastException = new AssertionFailedError("Service disconnected unexpectedly");
+ mLock.notify();
+ }
+ }
+
+ private class ReplyHandler extends Handler {
+ ReplyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mLock) {
+ mLastResult = msg.what;
+ if (msg.what == TestProcessService.REPLY_EXCEPTION) {
+ mLastException = (Throwable) msg.getData().getSerializable(
+ TestProcessService.REPLY_EXCEPTION_KEY);
+ }
+ mLock.notify();
+ }
+ }
+ }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
new file mode 100644
index 0000000..6ce8c4c
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+// Subclasses are the ones that get actually used, so make this abstract
+abstract class TestProcessService extends Service {
+ static final int MSG_RUN_TEST = 0;
+ static final int MSG_EXIT_PROCESS = 1;
+ static final String TEST_CLASS_KEY = "class";
+
+ static final int REPLY_OK = 0;
+ static final int REPLY_EXCEPTION = 1;
+ static final String REPLY_EXCEPTION_KEY = "exception";
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+
+ final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+ private class IncomingHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_EXIT_PROCESS) {
+ System.exit(0);
+ }
+
+ try {
+ if (msg.what != MSG_RUN_TEST) {
+ throw new AssertionFailedError("Unknown service message " + msg.what);
+ }
+
+ String testClassName = msg.getData().getString(TEST_CLASS_KEY);
+ Class testClass = Class.forName(testClassName);
+ TestProcessClient.TestRunnable test =
+ (TestProcessClient.TestRunnable) testClass.newInstance();
+ test.run(TestProcessService.this);
+ } catch (Throwable t) {
+ try {
+ Message m = Message.obtain(null, REPLY_EXCEPTION);
+ m.getData().putSerializable(REPLY_EXCEPTION_KEY, t);
+ msg.replyTo.send(m);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ return;
+ }
+
+ try {
+ msg.replyTo.send(Message.obtain(null, REPLY_OK));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceA.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceA.java
new file mode 100644
index 0000000..14241f0
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceA.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+// We need two different instances of the same service, so make two subclasses.
+public class TestProcessServiceA extends TestProcessService {}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceB.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceB.java
new file mode 100644
index 0000000..d2376f7
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessServiceB.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+// We need two different instances of the same service, so make two subclasses.
+public class TestProcessServiceB extends TestProcessService {}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
new file mode 100644
index 0000000..0843eaf
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.webkit.CookieManager;
+import android.webkit.WebView;
+
+import com.android.compatibility.common.util.NullWebViewUtils;
+
+public class WebViewDataDirTest extends AndroidTestCase {
+
+ private static final long REMOTE_TIMEOUT_MS = 5000;
+ private static final String ALTERNATE_DIR_NAME = "test";
+ private static final String COOKIE_URL = "https://www.example.com/";
+ private static final String COOKIE_VALUE = "foo=main";
+ private static final String SET_COOKIE_PARAMS = "; Max-Age=86400";
+
+ static class TestDisableThenUseImpl extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ WebView.disableWebView();
+ try {
+ new WebView(ctx);
+ fail("didn't throw IllegalStateException");
+ } catch (IllegalStateException e) {}
+ }
+ }
+
+ public void testDisableThenUse() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ try (TestProcessClient process = TestProcessClient.createProcessA(getContext())) {
+ process.run(TestDisableThenUseImpl.class, REMOTE_TIMEOUT_MS);
+ }
+ }
+
+ static class TestUseThenDisableImpl extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ new WebView(ctx);
+ try {
+ WebView.disableWebView();
+ fail("didn't throw IllegalStateException");
+ } catch (IllegalStateException e) {}
+ }
+ }
+
+ public void testUseThenDisable() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ try (TestProcessClient process = TestProcessClient.createProcessA(getContext())) {
+ process.run(TestUseThenDisableImpl.class, REMOTE_TIMEOUT_MS);
+ }
+ }
+
+ static class TestUseThenChangeDirImpl extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ new WebView(ctx);
+ try {
+ WebView.setDataDirectorySuffix("test");
+ fail("didn't throw IllegalStateException");
+ } catch (IllegalStateException e) {}
+ }
+ }
+
+ public void testUseThenChangeDir() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ try (TestProcessClient process = TestProcessClient.createProcessA(getContext())) {
+ process.run(TestUseThenChangeDirImpl.class, REMOTE_TIMEOUT_MS);
+ }
+ }
+
+ static class TestInvalidDirImpl extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ try {
+ WebView.setDataDirectorySuffix("no/path/separators");
+ fail("didn't throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {}
+ }
+ }
+
+ public void testInvalidDir() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ try (TestProcessClient process = TestProcessClient.createProcessA(getContext())) {
+ process.run(TestInvalidDirImpl.class, REMOTE_TIMEOUT_MS);
+ }
+ }
+
+ static class WebViewInDefaultDir extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ new WebView(ctx);
+ }
+ }
+
+ static class TestDefaultDirDisallowed extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ try {
+ new WebView(ctx);
+ fail("didn't throw RuntimeException");
+ } catch (RuntimeException e) {}
+ }
+ }
+
+ public void testSameDirTwoProcesses() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ try (TestProcessClient processA = TestProcessClient.createProcessA(getContext());
+ TestProcessClient processB = TestProcessClient.createProcessB(getContext())) {
+ processA.run(WebViewInDefaultDir.class, REMOTE_TIMEOUT_MS);
+ processB.run(TestDefaultDirDisallowed.class, REMOTE_TIMEOUT_MS);
+ }
+ }
+
+ static class SetCookieInDefaultDir extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ CookieManager cm = CookieManager.getInstance();
+ cm.setCookie(COOKIE_URL, COOKIE_VALUE + SET_COOKIE_PARAMS);
+ cm.flush();
+ }
+ }
+
+ static class TestCookieInDefaultDir extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ CookieManager cm = CookieManager.getInstance();
+ String cookie = cm.getCookie(COOKIE_URL);
+ assertEquals("wrong cookie in default cookie jar", COOKIE_VALUE, cookie);
+ }
+ }
+
+ static class TestCookieInAlternateDir extends TestProcessClient.TestRunnable {
+ @Override
+ public void run(Context ctx) {
+ WebView.setDataDirectorySuffix(ALTERNATE_DIR_NAME);
+ CookieManager cm = CookieManager.getInstance();
+ String cookie = cm.getCookie(COOKIE_URL);
+ assertNull("cookie leaked to alternate cookie jar", cookie);
+ }
+ }
+
+ public void testCookieJarsSeparate() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+
+ try (TestProcessClient processA = TestProcessClient.createProcessA(getContext());
+ TestProcessClient processB = TestProcessClient.createProcessB(getContext())) {
+ processA.run(SetCookieInDefaultDir.class, REMOTE_TIMEOUT_MS);
+ processA.run(TestCookieInDefaultDir.class, REMOTE_TIMEOUT_MS);
+ processB.run(TestCookieInAlternateDir.class, REMOTE_TIMEOUT_MS);
+ }
+ }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index 0708568..688fc33 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -898,8 +898,8 @@
}
private void clearClientCertPreferences() {
- final AtomicBoolean cleared = new AtomicBoolean(false);
- mOnUiThread.clearClientCertPreferences(new Runnable() {
+ final AtomicBoolean cleared = new AtomicBoolean(false);
+ WebView.clearClientCertPreferences(new Runnable() {
@Override
public void run() {
cleared.set(true);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
old mode 100755
new mode 100644
index e5939fd..32be1c9
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -27,6 +27,7 @@
import android.graphics.Color;
import android.graphics.Picture;
import android.graphics.Rect;
+import android.graphics.pdf.PdfRenderer;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -38,6 +39,7 @@
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
@@ -212,6 +214,7 @@
StrictMode.setThreadPolicy(oldPolicy);
}
+ @Presubmit
@UiThreadTest
public void testConstructor() {
if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -272,7 +275,7 @@
assertNotNull(CookieSyncManager.getInstance());
}
- @UiThreadTest
+ // Static methods should be safe to call on non-UI threads
public void testFindAddress() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
@@ -2562,7 +2565,8 @@
// Called on UI thread
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
- savePrintedPage(adapter, descriptor, result);
+ PageRange[] pageRanges = new PageRange[] {PageRange.ALL_PAGES};
+ savePrintedPage(adapter, descriptor, pageRanges, result);
}
});
try {
@@ -2580,6 +2584,57 @@
}
}
+ // Verify Print feature can create a PDF file with correct number of pages.
+ public void testPrintingPagesCount() throws Throwable {
+ if (!NullWebViewUtils.isWebViewAvailable()) {
+ return;
+ }
+ String content = "<html><head></head><body>";
+ for (int i = 0; i < 500; ++i) {
+ content += "<br />abcdefghijk<br />";
+ }
+ content += "</body></html>";
+ mOnUiThread.loadDataAndWaitForCompletion(content, "text/html", null);
+ final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter();
+ printDocumentStart(adapter);
+ PrintAttributes attributes = new PrintAttributes.Builder()
+ .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
+ .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
+ .setMinMargins(PrintAttributes.Margins.NO_MARGINS)
+ .build();
+ final WebViewCtsActivity activity = getActivity();
+ final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
+ final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.parseMode("w"));
+ final FutureTask<Boolean> result =
+ new FutureTask<Boolean>(new Callable<Boolean>() {
+ public Boolean call() {
+ return true;
+ }
+ });
+ printDocumentLayout(adapter, null, attributes,
+ new LayoutResultCallback() {
+ // Called on UI thread
+ @Override
+ public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+ PageRange[] pageRanges = new PageRange[] {
+ new PageRange(1, 1), new PageRange(4, 7)
+ };
+ savePrintedPage(adapter, descriptor, pageRanges, result);
+ }
+ });
+ try {
+ result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+ assertTrue(file.length() > 0);
+ PdfRenderer renderer = new PdfRenderer(
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
+ assertEquals(5, renderer.getPageCount());
+ } finally {
+ descriptor.close();
+ file.delete();
+ }
+ }
+
public void testVisualStateCallbackCalled() throws Exception {
// Check that the visual state callback is called correctly.
if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -2808,8 +2863,9 @@
}
private void savePrintedPage(final PrintDocumentAdapter adapter,
- final ParcelFileDescriptor descriptor, final FutureTask<Boolean> result) {
- adapter.onWrite(new PageRange[] {PageRange.ALL_PAGES}, descriptor,
+ final ParcelFileDescriptor descriptor, final PageRange[] pageRanges,
+ final FutureTask<Boolean> result) {
+ adapter.onWrite(pageRanges, descriptor,
new CancellationSignal(),
new WriteResultCallback() {
@Override
diff --git a/tests/tests/widget/Android.mk b/tests/tests/widget/Android.mk
index ec104d8..d18ec64 100644
--- a/tests/tests/widget/Android.mk
+++ b/tests/tests/widget/Android.mk
@@ -27,8 +27,7 @@
android-common \
compatibility-device-util \
ctstestrunner \
- platform-test-annotations \
- legacy-android-test
+ platform-test-annotations
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index f7fa1d4..3300265 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -19,6 +19,7 @@
package="android.widget.cts">
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application android:label="Android TestCase"
android:icon="@drawable/size_48x48"
@@ -329,6 +330,16 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.cts.TextClockCtsActivity"
+ android:label="TextClockCtsActivity"
+ android:screenOrientation="nosensor"
+ android:windowSoftInputMode="stateAlwaysHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.cts.TextViewCtsActivity"
android:label="TextViewCtsActivity"
android:screenOrientation="nosensor"
@@ -570,6 +581,14 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.cts.MagnifierCtsActivity"
+ android:label="MagnifierCtsActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<receiver android:name="android.widget.cts.appwidget.MyAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
diff --git a/tests/tests/widget/AndroidTest.xml b/tests/tests/widget/AndroidTest.xml
index 2eb0b86..a00e5c9 100644
--- a/tests/tests/widget/AndroidTest.xml
+++ b/tests/tests/widget/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Widget test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="uitoolkit" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/tests/widget/res/layout/autocompletetextview_layout.xml b/tests/tests/widget/res/layout/autocompletetextview_layout.xml
index 793dfb0..32eefb7 100644
--- a/tests/tests/widget/res/layout/autocompletetextview_layout.xml
+++ b/tests/tests/widget/res/layout/autocompletetextview_layout.xml
@@ -18,6 +18,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <requestFocus />
<TextView android:id="@+id/autocompletetv_title"
android:layout_width="wrap_content"
diff --git a/tests/tests/widget/res/layout/list_popup_window.xml b/tests/tests/widget/res/layout/list_popup_window.xml
index bcf5893..fd9dcdf 100644
--- a/tests/tests/widget/res/layout/list_popup_window.xml
+++ b/tests/tests/widget/res/layout/list_popup_window.xml
@@ -19,6 +19,7 @@
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <requestFocus />
<android.widget.cts.MockViewForListPopupWindow
android:id="@+id/anchor_upper_left"
diff --git a/tests/tests/widget/res/layout/magnifier_layout.xml b/tests/tests/widget/res/layout/magnifier_layout.xml
new file mode 100644
index 0000000..989d33d
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/magnifier_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textclock_layout.xml b/tests/tests/widget/res/layout/textclock_layout.xml
new file mode 100644
index 0000000..a1acce8
--- /dev/null
+++ b/tests/tests/widget/res/layout/textclock_layout.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextClock
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textclock"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/tests/tests/widget/res/layout/textview_fallbacklinespacing_layout.xml b/tests/tests/widget/res/layout/textview_fallbacklinespacing_layout.xml
new file mode 100644
index 0000000..684d821
--- /dev/null
+++ b/tests/tests/widget/res/layout/textview_fallbacklinespacing_layout.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/layout_textviewtest"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/textview_true"
+ android:fallbackLineSpacing="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView android:id="@+id/textview_false"
+ android:fallbackLineSpacing="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView android:id="@+id/textview_default"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView android:id="@+id/textview_style_true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.FallbackLineSpacingTrue"/>
+
+ <TextView android:id="@+id/textview_style_false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.FallbackLineSpacingFalse"/>
+
+ <TextView android:id="@+id/textview_style_default"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance"/>
+
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index d311d75..a616637 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -46,12 +46,14 @@
<TextView
android:id="@+id/textview_password"
android:password="true"
+ android:minWidth="1dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/textview_singleLine"
android:singleLine="true"
+ android:minWidth="1dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@@ -59,12 +61,14 @@
android:id="@+id/textview_text"
android:text="@string/text_view_hello"
android:breakStrategy="simple"
+ android:minWidth="1dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/textview_text_two_lines"
android:text="@string/text_view_hello_two_lines"
+ android:minWidth="1dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@@ -383,6 +387,52 @@
android:text="@string/sample_text"
android:justificationMode="inter_word" />
+ <TextView
+ android:id="@+id/textview_textappearance_attrs1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ style="@null"
+ android:textAppearance="@style/TextAppearance.Xml1" />
+
+ <TextView
+ android:id="@+id/textview_textappearance_attrs2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ style="@null"
+ android:textAppearance="@style/TextAppearance.Xml2" />
+
+ <TextView
+ android:id="@+id/textview_textappearance_attrs3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ android:textAppearance="@style/TextAppearance.Xml2"
+ style="@style/TextAppearance.Xml3" />
+
+ <TextView
+ android:id="@+id/textview_textappearance_attrs4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ style="@style/TextAppearance.Xml3" />
+
+ <TextView
+ android:id="@+id/textview_textappearance_attrs_allcaps_password"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ style="@style/AllCapsPassword" />
+
+ <TextView
+ android:id="@+id/textview_textappearance_attrs_serif_fontfamily"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text"
+ android:typeface="serif"
+ android:textAppearance="@style/TextAppearance.Xml2" />
+
</LinearLayout>
</ScrollView>
diff --git a/tests/tests/widget/res/menu/popup_menu.xml b/tests/tests/widget/res/menu/popup_menu.xml
index 29daad0..a2653b4 100644
--- a/tests/tests/widget/res/menu/popup_menu.xml
+++ b/tests/tests/widget/res/menu/popup_menu.xml
@@ -18,12 +18,14 @@
android:title="@string/popup_menu_highlight"
android:contentDescription="@string/popup_menu_highlight_description"
android:tooltipText="@string/popup_menu_highlight_tooltip" />
- <item android:id="@+id/action_edit"
- android:title="@string/popup_menu_edit"
- android:contentDescription="@string/popup_menu_edit_description"
- android:tooltipText="@string/popup_menu_edit_tooltip" />
- <item android:id="@+id/action_delete"
- android:title="@string/popup_menu_delete" />
+ <group android:id="@+id/group_test2">
+ <item android:id="@+id/action_edit"
+ android:title="@string/popup_menu_edit"
+ android:contentDescription="@string/popup_menu_edit_description"
+ android:tooltipText="@string/popup_menu_edit_tooltip" />
+ <item android:id="@+id/action_delete"
+ android:title="@string/popup_menu_delete" />
+ </group>
<item android:id="@+id/action_ignore"
android:title="@string/popup_menu_ignore" />
<item android:id="@+id/action_share"
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index a47f169..abb85ad 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -109,6 +109,52 @@
<item name="android:textStyle">normal</item>
</style>
+ <style name="TextAppearance.Xml1">
+ <item name="android:textSize">22px</item>
+ <item name="android:typeface">sans</item>
+ <item name="android:textStyle">italic</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:letterSpacing">2.4</item>
+ <item name="android:fontFeatureSettings">smcp</item>
+ </style>
+
+ <style name="TextAppearance.Xml2">
+ <item name="android:fontFamily">monospace</item>
+ <item name="android:textStyle">normal</item>
+ <item name="android:shadowColor">@drawable/red</item>
+ <item name="android:shadowDx">10.3</item>
+ <item name="android:shadowDy">0.5</item>
+ <item name="android:shadowRadius">3.3</item>
+ <item name="android:elegantTextHeight">true</item>
+ </style>
+
+ <style name="TextAppearance.Xml3">
+ <item name="android:textSize">32px</item>
+ <item name="android:fontFamily">serif</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:letterSpacing">2.6</item>
+ <item name="android:shadowColor">@drawable/blue</item>
+ <item name="android:shadowDx">1.3</item>
+ <item name="android:shadowDy">10.5</item>
+ <item name="android:shadowRadius">5.3</item>
+ <item name="android:elegantTextHeight">false</item>
+ </style>
+
+
+ <style name="TextAppearance.FallbackLineSpacingFalse">
+ <item name="android:fallbackLineSpacing">false</item>
+ </style>
+
+ <style name="TextAppearance.FallbackLineSpacingTrue">
+ <item name="android:fallbackLineSpacing">true</item>
+ </style>
+
+ <style name="AllCapsPassword">
+ <item name="android:textAllCaps">true</item>
+ <item name="android:password">true</item>
+ </style>
+
<style name="TestEnum1">
<item name="testEnum">val1</item>
</style>
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 9cb9903..e6cf9cc 100644
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -30,6 +30,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.text.Editable;
import android.text.TextUtils;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
@@ -360,4 +361,24 @@
return super.getDefaultMovementMethod();
}
}
+
+ @Test
+ public void testGetTextNonEditable() {
+ // This subclass calls getText before the object is fully constructed. This should not cause
+ // a null pointer exception.
+ GetTextEditText editText = new GetTextEditText(mActivity);
+ }
+
+ private class GetTextEditText extends EditText {
+
+ GetTextEditText(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setText(CharSequence text, BufferType type) {
+ Editable currentText = getText();
+ super.setText(text, type);
+ }
+ }
}
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index 95e3581..189a0c9 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -608,6 +608,7 @@
final AttachDetachAwareView theView = new AttachDetachAwareView(mActivity);
WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mGridView, () -> {
mGridView.setAdapter(new DummyAdapter(1000, theView));
+ mGridView.requestFocus();
});
assertEquals("test sanity", 1, theView.mOnAttachCount);
assertEquals("test sanity", 0, theView.mOnDetachCount);
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java b/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
new file mode 100644
index 0000000..a828c3d
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierCtsActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A minimal application for {@link MagnifierTest}.
+ */
+public class MagnifierCtsActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.magnifier_layout);
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
new file mode 100644
index 0000000..46d3fe5
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.app.Activity;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.Magnifier;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link Magnifier}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MagnifierTest {
+ private Activity mActivity;
+ private LinearLayout mMagnifierLayout;
+
+ @Rule
+ public ActivityTestRule<MagnifierCtsActivity> mActivityRule =
+ new ActivityTestRule<>(MagnifierCtsActivity.class);
+
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ PollingCheck.waitFor(mActivity::hasWindowFocus);
+ mMagnifierLayout = mActivity.findViewById(R.id.magnifier_layout);
+ }
+
+ @Test
+ public void testConstructor() {
+ new Magnifier(new View(mActivity));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testConstructor_NPE() {
+ new Magnifier(null);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testShow() {
+ View view = new View(mActivity);
+ mMagnifierLayout.addView(view, new LayoutParams(200, 200));
+ Magnifier magnifier = new Magnifier(view);
+ // Valid coordinates.
+ magnifier.show(0, 0);
+ // Invalid coordinates, should both be clamped to 0.
+ magnifier.show(-1, -1);
+ // Valid coordinates.
+ magnifier.show(10, 10);
+ // Same valid coordinate as above, should skip making another copy request.
+ magnifier.show(10, 10);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testDismiss() {
+ View view = new View(mActivity);
+ mMagnifierLayout.addView(view, new LayoutParams(200, 200));
+ Magnifier magnifier = new Magnifier(view);
+ // Valid coordinates.
+ magnifier.show(10, 10);
+ magnifier.dismiss();
+ // Should be no-op.
+ magnifier.dismiss();
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
index 6640cc4..7392c99 100644
--- a/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/NumberPickerTest.java
@@ -32,6 +32,7 @@
import android.content.res.Configuration;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@@ -47,6 +48,7 @@
import org.junit.runner.RunWith;
import org.mockito.InOrder;
+@FlakyTest
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NumberPickerTest {
diff --git a/tests/tests/widget/src/android/widget/cts/OWNERS b/tests/tests/widget/src/android/widget/cts/OWNERS
new file mode 100644
index 0000000..84f2ef0
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/OWNERS
@@ -0,0 +1,8 @@
+per-file TextView*.java = siyamed@google.com
+per-file TextView*.java = nona@google.com
+per-file TextView*.java = clarabayarri@google.com
+per-file TextView*.java = toki@google.com
+per-file EditText*.java = siyamed@google.com
+per-file EditText*.java = nona@google.com
+per-file EditText*.java = clarabayarri@google.com
+per-file EditText*.java = toki@google.com
diff --git a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
index 5ce73a8..3a85f47 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
@@ -40,6 +40,7 @@
import android.view.SubMenu;
import android.view.View;
import android.widget.EditText;
+import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupMenu;
@@ -273,7 +274,7 @@
@Test
public void testItemViewAttributes() throws Throwable {
- mBuilder = new Builder().withDismissListener();
+ mBuilder = new Builder().withDismissListener().withAnchorId(R.id.anchor_upper_left);
WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, mBuilder::show, true);
Menu menu = mPopupMenu.getMenu();
@@ -303,6 +304,38 @@
}
}
+ @Test
+ public void testGroupDividerEnabledAPI() throws Throwable {
+ testGroupDivider(false);
+ testGroupDivider(true);
+ }
+
+ private void testGroupDivider(boolean groupDividerEnabled) throws Throwable {
+ mBuilder = new Builder().withGroupDivider(groupDividerEnabled)
+ .withAnchorId(R.id.anchor_upper_left);
+ WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, mBuilder::show, true);
+
+ Menu menu = mPopupMenu.getMenu();
+ ListView menuItemList = mPopupMenu.getMenuListView();
+
+ for (int i = 0; i < menuItemList.getChildCount(); i++) {
+ final int currGroupId = menu.getItem(i).getGroupId();
+ final int prevGroupId =
+ i - 1 >= 0 ? menu.getItem(i - 1).getGroupId() : currGroupId;
+ View itemView = menuItemList.getChildAt(i);
+ ImageView groupDivider = itemView.findViewById(com.android.internal.R.id.group_divider);
+
+ assertNotNull(groupDivider);
+ if (!groupDividerEnabled || currGroupId == prevGroupId) {
+ assertEquals(groupDivider.getVisibility(), View.GONE);
+ } else {
+ assertEquals(groupDivider.getVisibility(), View.VISIBLE);
+ }
+ }
+
+ teardown();
+ }
+
/**
* Inner helper class to configure an instance of {@link PopupMenu} for the specific test.
* The main reason for its existence is that once a popup menu is shown with the show() method,
@@ -315,6 +348,7 @@
private boolean mHasMenuItemClickListener;
private boolean mInflateWithInflater;
+ private int mAnchorId = R.id.anchor_middle_left;
private int mPopupMenuContent = R.menu.popup_menu;
private boolean mUseCustomPopupResource;
@@ -328,6 +362,8 @@
private View mAnchor;
+ private boolean mGroupDividerEnabled = false;
+
public Builder withMenuItemClickListener() {
mHasMenuItemClickListener = true;
return this;
@@ -360,8 +396,18 @@
return this;
}
+ public Builder withGroupDivider(boolean groupDividerEnabled) {
+ mGroupDividerEnabled = groupDividerEnabled;
+ return this;
+ }
+
+ public Builder withAnchorId(int anchorId) {
+ mAnchorId = anchorId;
+ return this;
+ }
+
private void configure() {
- mAnchor = mActivity.findViewById(R.id.anchor_middle_left);
+ mAnchor = mActivity.findViewById(mAnchorId);
if (!mUseCustomGravity && !mUseCustomPopupResource) {
mPopupMenu = new PopupMenu(mActivity, mAnchor);
} else if (!mUseCustomPopupResource) {
@@ -390,6 +436,10 @@
mOnDismissListener = mock(PopupMenu.OnDismissListener.class);
mPopupMenu.setOnDismissListener(mOnDismissListener);
}
+
+ if (mGroupDividerEnabled) {
+ mPopupMenu.getMenu().setGroupDividerEnabled(true);
+ }
}
public void show() {
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 330a92a..01fcccd 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -40,6 +40,7 @@
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
@@ -68,6 +69,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+@FlakyTest
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PopupWindowTest {
@@ -1330,6 +1332,9 @@
mActivity.runOnUiThread(() ->
mActivity.setRequestedOrientation(orientation));
mActivity.waitForConfigurationChanged();
+ // Wait for main thread to be idle to make sure layout and draw have been performed
+ // before continuing.
+ mInstrumentation.waitForIdleSync();
View parentWindowView = mActivity.getWindow().getDecorView();
int parentWidth = parentWindowView.getMeasuredWidth();
@@ -1398,6 +1403,9 @@
mPopupWindow.getContentView().getRootView(),
() -> container.scrollBy(deltaX, deltaY),
false /* force layout */);
+ // Since the first layout might have been caused by the original scroll event (and not by
+ // the anchor change), we need to wait until all traversals are done.
+ mInstrumentation.waitForIdleSync();
assertPopupLocation(originalLocation, deltaX, deltaY);
// Detach the anchor, the popup should stay in the same location.
@@ -1414,6 +1422,7 @@
mActivity.getWindow().getDecorView(),
() -> container.scrollBy(deltaX, deltaY),
true /* force layout */);
+ mInstrumentation.waitForIdleSync();
assertPopupLocation(originalLocation, deltaX, deltaY);
// Re-attach the anchor, the popup should snap back to the new anchor location.
@@ -1530,6 +1539,10 @@
() -> container.scrollBy(deltaX, deltaY),
false /* force layout */);
+ // Since the first layout might have been caused by the original scroll event (and not by
+ // the anchor change), we need to wait until all traversals are done.
+ mInstrumentation.waitForIdleSync();
+
final int[] newPopupLocation = mPopupWindow.getContentView().getLocationOnScreen();
assertEquals(popupLocation[0] - deltaX, newPopupLocation[0]);
assertEquals(popupLocation[1] - deltaY, newPopupLocation[1]);
diff --git a/tests/tests/widget/src/android/widget/cts/TextClockCtsActivity.java b/tests/tests/widget/src/android/widget/cts/TextClockCtsActivity.java
new file mode 100644
index 0000000..43e3582
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextClockCtsActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextClock;
+
+/**
+ * A minimal application for {@link TextClock} test.
+ */
+public class TextClockCtsActivity extends Activity {
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.textclock_layout);
+ }
+}
+
diff --git a/tests/tests/widget/src/android/widget/cts/TextClockTest.java b/tests/tests/widget/src/android/widget/cts/TextClockTest.java
new file mode 100644
index 0000000..acb6796
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextClockTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.format.DateFormat;
+import android.util.MutableBoolean;
+import android.widget.TextClock;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link TextClock}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TextClockTest {
+ private Activity mActivity;
+ private TextClock mTextClock;
+ private boolean mStartedAs24;
+
+ @Rule
+ public ActivityTestRule<TextClockCtsActivity> mActivityRule =
+ new ActivityTestRule<>(TextClockCtsActivity.class);
+
+ @Before
+ public void setup() throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ mTextClock = mActivity.findViewById(R.id.textclock);
+ mStartedAs24 = DateFormat.is24HourFormat(mActivity);
+ }
+
+ public void teardown() throws Throwable {
+ int base = mStartedAs24 ? 24 : 12;
+ Settings.System.putInt(mActivity.getContentResolver(), Settings.System.TIME_12_24, base);
+ }
+
+ @Test
+ public void testUpdate12_24() throws Throwable {
+ grantWriteSettingsPermission();
+
+ mActivityRule.runOnUiThread(() -> {
+ mTextClock.setFormat12Hour("h");
+ mTextClock.setFormat24Hour("H");
+ mTextClock.disableClockTick();
+ });
+
+ final ContentResolver resolver = mActivity.getContentResolver();
+ Calendar mNow = Calendar.getInstance();
+ mNow.setTimeInMillis(System.currentTimeMillis()); // just like TextClock uses
+
+ // make sure the clock is showing some time > 12pm and not near midnight
+ for (String id : TimeZone.getAvailableIDs()) {
+ final TimeZone timeZone = TimeZone.getTimeZone(id);
+ mNow.setTimeZone(timeZone);
+ int hour = mNow.get(Calendar.HOUR_OF_DAY);
+ if (hour < 22 && hour > 12) {
+ mActivityRule.runOnUiThread(() -> {
+ mTextClock.setTimeZone(id);
+ });
+ break;
+ }
+ }
+
+ final CountDownLatch change12 = registerForChanges(Settings.System.TIME_12_24);
+ mActivityRule.runOnUiThread(() -> {
+ Settings.System.putInt(resolver, Settings.System.TIME_12_24, 12);
+ });
+ assertTrue(change12.await(1, TimeUnit.SECONDS));
+
+ // Must poll here because there are no timing guarantees for ContentObserver notification
+ PollingCheck.waitFor(() -> {
+ final MutableBoolean ok = new MutableBoolean(false);
+ try {
+ mActivityRule.runOnUiThread(() -> {
+ int hour = Integer.parseInt(mTextClock.getText().toString());
+ ok.value = hour >= 1 && hour < 12;
+ });
+ } catch (Throwable t) {
+ throw new RuntimeException(t.getMessage());
+ }
+ return ok.value;
+ });
+
+ final CountDownLatch change24 = registerForChanges(Settings.System.TIME_12_24);
+ mActivityRule.runOnUiThread(() -> {
+ Settings.System.putInt(resolver, Settings.System.TIME_12_24, 24);
+ });
+ assertTrue(change24.await(1, TimeUnit.SECONDS));
+
+ // Must poll here because there are no timing guarantees for ContentObserver notification
+ PollingCheck.waitFor(() -> {
+ final MutableBoolean ok = new MutableBoolean(false);
+ try {
+ mActivityRule.runOnUiThread(() -> {
+ int hour = Integer.parseInt(mTextClock.getText().toString());
+ ok.value = hour > 12 && hour < 24;
+ });
+ return ok.value;
+ } catch (Throwable t) {
+ throw new RuntimeException(t.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testNoChange() throws Throwable {
+ grantWriteSettingsPermission();
+ mActivityRule.runOnUiThread(() -> {
+ mTextClock.disableClockTick();
+ });
+ final ContentResolver resolver = mActivity.getContentResolver();
+
+ // Now test that it isn't updated when a non-12/24 hour setting is set
+ mActivityRule.runOnUiThread(() -> {
+ mTextClock.setText("Nothing");
+ });
+
+ mActivityRule.runOnUiThread(() -> {
+ assertEquals("Nothing", mTextClock.getText().toString());
+ });
+
+ final CountDownLatch otherChange = registerForChanges(Settings.System.TEXT_AUTO_CAPS);
+ mActivityRule.runOnUiThread(() -> {
+ int oldAutoCaps = Settings.System.getInt(resolver, Settings.System.TEXT_AUTO_CAPS,
+ 1);
+ try {
+ int newAutoCaps = oldAutoCaps == 0 ? 1 : 0;
+ Settings.System.putInt(resolver, Settings.System.TEXT_AUTO_CAPS, newAutoCaps);
+ } finally {
+ Settings.System.putInt(resolver, Settings.System.TEXT_AUTO_CAPS, oldAutoCaps);
+ }
+ });
+
+ assertTrue(otherChange.await(1, TimeUnit.SECONDS));
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ mActivityRule.runOnUiThread(() -> {
+ assertEquals("Nothing", mTextClock.getText().toString());
+ });
+ }
+
+ private CountDownLatch registerForChanges(String setting) throws Throwable {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mActivityRule.runOnUiThread(() -> {
+ final ContentResolver resolver = mActivity.getContentResolver();
+ Uri uri = Settings.System.getUriFor(setting);
+ resolver.registerContentObserver(uri, true,
+ new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ countDownAndRemove();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ countDownAndRemove();
+ }
+
+ private void countDownAndRemove() {
+ latch.countDown();
+ resolver.unregisterContentObserver(this);
+ }
+ });
+ });
+ return latch;
+ }
+
+ private void grantWriteSettingsPermission() throws IOException {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "appops set " + mActivity.getPackageName() + " "
+ + AppOpsManager.OPSTR_WRITE_SETTINGS + " allow");
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index ac90cb7..cc4d023 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -85,10 +85,12 @@
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
+import android.text.PremeasuredText;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
+import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
@@ -139,7 +141,6 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextSelection;
import android.widget.EditText;
@@ -190,15 +191,9 @@
private static final TextClassifier FAKE_TEXT_CLASSIFIER = new TextClassifier() {
@Override
public TextSelection suggestSelection(
- CharSequence text, int start, int end, LocaleList locales) {
+ CharSequence text, int start, int end, TextSelection.Options options) {
return new TextSelection.Builder(SMARTSELECT_START, SMARTSELECT_END).build();
}
-
- @Override
- public TextClassification classifyText(
- CharSequence text, int start, int end, LocaleList locales) {
- return new TextClassification.Builder().build();
- }
};
private static final int CLICK_TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 50;
@@ -1640,6 +1635,16 @@
}
}
+ @UiThreadTest
+ @Test
+ public void testSetText_PremeasuredText() {
+ final TextView tv = findTextView(R.id.textview_text);
+ final PremeasuredText premeasured = PremeasuredText.build(
+ "This is an example text.", new TextPaint(), TextDirectionHeuristics.LTR);
+ tv.setText(premeasured);
+ assertEquals(premeasured.toString(), tv.getText().toString());
+ }
+
@Test
public void testSetTextUpdatesHeightAfterRemovingImageSpan() throws Throwable {
// Height calculation had problems when TextView had width: match_parent
@@ -4445,6 +4450,75 @@
assertEquals(null, mTextView.getTypeface());
}
+ @Test
+ public void testXmlTextAppearance() {
+ mTextView = findTextView(R.id.textview_textappearance_attrs1);
+ assertEquals(22f, mTextView.getTextSize(), 0.01f);
+ Typeface italicSans = Typeface.create(Typeface.SANS_SERIF, Typeface.ITALIC);
+ assertEquals(italicSans, mTextView.getTypeface());
+ assertEquals(Typeface.ITALIC, mTextView.getTypeface().getStyle());
+ assertTrue(mTextView.isAllCaps());
+ assertEquals(2.4f, mTextView.getLetterSpacing(), 0.01f);
+ assertEquals("smcp", mTextView.getFontFeatureSettings());
+
+ mTextView = findTextView(R.id.textview_textappearance_attrs2);
+ assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+ assertEquals(mActivity.getResources().getColor(R.drawable.red),
+ mTextView.getShadowColor());
+ assertEquals(10.3f, mTextView.getShadowDx(), 0.01f);
+ assertEquals(0.5f, mTextView.getShadowDy(), 0.01f);
+ assertEquals(3.3f, mTextView.getShadowRadius(), 0.01f);
+ assertTrue(mTextView.isElegantTextHeight());
+
+ // This TextView has both a TextAppearance and a style, so the style should override.
+ mTextView = findTextView(R.id.textview_textappearance_attrs3);
+ assertEquals(32f, mTextView.getTextSize(), 0.01f);
+ Typeface boldSerif = Typeface.create(Typeface.SERIF, Typeface.BOLD);
+ assertEquals(boldSerif, mTextView.getTypeface());
+ assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
+ assertFalse(mTextView.isAllCaps());
+ assertEquals(2.6f, mTextView.getLetterSpacing(), 0.01f);
+ assertEquals(mActivity.getResources().getColor(R.drawable.blue),
+ mTextView.getShadowColor());
+ assertEquals(1.3f, mTextView.getShadowDx(), 0.01f);
+ assertEquals(10.5f, mTextView.getShadowDy(), 0.01f);
+ assertEquals(5.3f, mTextView.getShadowRadius(), 0.01f);
+ assertFalse(mTextView.isElegantTextHeight());
+
+ // This TextView has no TextAppearance and has a style, so the style should be applied.
+ mTextView = findTextView(R.id.textview_textappearance_attrs4);
+ assertEquals(32f, mTextView.getTextSize(), 0.01f);
+ assertEquals(boldSerif, mTextView.getTypeface());
+ assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
+ assertFalse(mTextView.isAllCaps());
+ assertEquals(2.6f, mTextView.getLetterSpacing(), 0.01f);
+ assertEquals(mActivity.getResources().getColor(R.drawable.blue),
+ mTextView.getShadowColor());
+ assertEquals(1.3f, mTextView.getShadowDx(), 0.01f);
+ assertEquals(10.5f, mTextView.getShadowDy(), 0.01f);
+ assertEquals(5.3f, mTextView.getShadowRadius(), 0.01f);
+ assertFalse(mTextView.isElegantTextHeight());
+
+ // Note: text, link and hint colors can't be tested due to the default style overriding
+ // values b/63923542
+ }
+
+ @Test
+ public void testXmlTypefaceFontFamilyHierarchy() {
+ // This view has typeface=serif set on the view directly and a fontFamily on the appearance.
+ // In this case, the attr set directly on the view should take precedence.
+ mTextView = findTextView(R.id.textview_textappearance_attrs_serif_fontfamily);
+
+ assertEquals(Typeface.SERIF, mTextView.getTypeface());
+ }
+
+ @Test
+ public void testAttributeReading_allCapsAndPassword() {
+ // This TextView has all caps & password, therefore all caps should be ignored.
+ mTextView = findTextView(R.id.textview_textappearance_attrs_allcaps_password);
+ assertFalse(mTextView.isAllCaps());
+ }
+
@UiThreadTest
@Test
public void testAccessCompoundDrawableTint() {
@@ -7743,6 +7817,70 @@
assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
}
+ @UiThreadTest
+ @Test
+ public void testFallbackLineSpacing_readsFromLayoutXml() {
+ mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+ mTextView = findTextView(R.id.textview_true);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView = findTextView(R.id.textview_default);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView = findTextView(R.id.textview_false);
+ assertFalse(mTextView.isFallbackLineSpacing());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFallbackLineSpacing_set_get() {
+ mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+ mTextView = findTextView(R.id.textview_true);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView.setFallbackLineSpacing(false);
+ assertFalse(mTextView.isFallbackLineSpacing());
+
+ mTextView.setFallbackLineSpacing(true);
+ assertTrue(mTextView.isFallbackLineSpacing());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFallbackLineSpacing_readsFromStyleXml() {
+ mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+ mTextView = findTextView(R.id.textview_style_true);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView = findTextView(R.id.textview_style_default);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView = findTextView(R.id.textview_style_false);
+ assertFalse(mTextView.isFallbackLineSpacing());
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFallbackLineSpacing_textAppearance_set_get() {
+ mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
+ mTextView = findTextView(R.id.textview_default);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView.setTextAppearance(R.style.TextAppearance_FallbackLineSpacingFalse);
+ assertFalse(mTextView.isFallbackLineSpacing());
+
+ mTextView.setTextAppearance(R.style.TextAppearance_FallbackLineSpacingTrue);
+ assertTrue(mTextView.isFallbackLineSpacing());
+
+ mTextView.setFallbackLineSpacing(false);
+ mTextView.setTextAppearance(R.style.TextAppearance);
+ assertFalse(mTextView.isFallbackLineSpacing());
+
+ mTextView.setFallbackLineSpacing(true);
+ mTextView.setTextAppearance(R.style.TextAppearance);
+ assertTrue(mTextView.isFallbackLineSpacing());
+ }
+
private void initializeTextForSmartSelection(CharSequence text) throws Throwable {
assertTrue(text.length() >= SMARTSELECT_END);
mActivityRule.runOnUiThread(() -> {
diff --git a/tests/tests/widget/src/android/widget/cts/util/ListScenario.java b/tests/tests/widget/src/android/widget/cts/util/ListScenario.java
index c4d4199..1d63c40 100644
--- a/tests/tests/widget/src/android/widget/cts/util/ListScenario.java
+++ b/tests/tests/widget/src/android/widget/cts/util/ListScenario.java
@@ -16,13 +16,6 @@
package android.widget.cts.util;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
@@ -36,6 +29,13 @@
import android.widget.ListView;
import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
/**
* Utility base class for creating various List scenarios. Configurable by the number
* of items, how tall each item should be (in relation to the screen height), and
@@ -390,6 +390,7 @@
mLinearLayout.addView(mListView);
setContentView(mLinearLayout);
}
+ mLinearLayout.restoreDefaultFocus();
}
/**
diff --git a/tests/tests/wrap/nowrap/Android.mk b/tests/tests/wrap/nowrap/Android.mk
index 35c0448..8fd1015 100644
--- a/tests/tests/wrap/nowrap/Android.mk
+++ b/tests/tests/wrap/nowrap/Android.mk
@@ -23,8 +23,8 @@
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- android-support-test \
- legacy-android-test
+ android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/wrap/nowrap/AndroidTest.xml b/tests/tests/wrap/nowrap/AndroidTest.xml
index 1ca2369..b80d4ee 100644
--- a/tests/tests/wrap/nowrap/AndroidTest.xml
+++ b/tests/tests/wrap/nowrap/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS No Wrap test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/wrap/wrap_debug/Android.mk b/tests/tests/wrap/wrap_debug/Android.mk
index 77b6d27..c67e191 100644
--- a/tests/tests/wrap/wrap_debug/Android.mk
+++ b/tests/tests/wrap/wrap_debug/Android.mk
@@ -23,8 +23,8 @@
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- android-support-test \
- legacy-android-test
+ android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/wrap/wrap_debug/AndroidTest.xml b/tests/tests/wrap/wrap_debug/AndroidTest.xml
index b3468bf..2b9cbe1 100644
--- a/tests/tests/wrap/wrap_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Debug Wrap test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
index bc6240d..b768dcf 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
@@ -23,10 +23,10 @@
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- android-support-test \
- legacy-android-test
+ android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
LOCAL_PREBUILT_JNI_LIBS_arm := wrap.sh
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
index 0c740e4..2faff68 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
@@ -14,7 +14,9 @@
limitations under the License.
-->
<configuration description="Config for CTS Debug Wrap (Malloc Debug) test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
+ <option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsWrapWrapDebugMallocDebugTestCases.apk" />
diff --git a/tests/tests/wrap/wrap_nodebug/Android.mk b/tests/tests/wrap/wrap_nodebug/Android.mk
index 1d6e9c0..3317f3c 100644
--- a/tests/tests/wrap/wrap_nodebug/Android.mk
+++ b/tests/tests/wrap/wrap_nodebug/Android.mk
@@ -23,8 +23,8 @@
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
compatibility-device-util \
- android-support-test \
- legacy-android-test
+ android-support-test
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
LOCAL_SDK_VERSION := current
diff --git a/tests/tests/wrap/wrap_nodebug/AndroidTest.xml b/tests/tests/wrap/wrap_nodebug/AndroidTest.xml
index b7a6a11..dc547cf 100644
--- a/tests/tests/wrap/wrap_nodebug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_nodebug/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Wrap test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tvprovider/Android.mk b/tests/tvprovider/Android.mk
index 4a13909..5595519 100644
--- a/tests/tvprovider/Android.mk
+++ b/tests/tvprovider/Android.mk
@@ -20,6 +20,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsTvProviderTestCases
diff --git a/tests/tvprovider/AndroidTest.xml b/tests/tvprovider/AndroidTest.xml
index 9365cbd..c62cec8 100644
--- a/tests/tvprovider/AndroidTest.xml
+++ b/tests/tvprovider/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS TV Provider test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="tv" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/ui/Android.mk b/tests/ui/Android.mk
index 53ae77e..abdc148 100644
--- a/tests/ui/Android.mk
+++ b/tests/ui/Android.mk
@@ -22,6 +22,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsUiDeviceTestCases
diff --git a/tests/video/Android.mk b/tests/video/Android.mk
index c70cfaf..b077e63 100644
--- a/tests/video/Android.mk
+++ b/tests/video/Android.mk
@@ -25,6 +25,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil compatibility-device-util ctstestrunner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+
LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni libnativehelper_compat_libc++
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/video/AndroidTest.xml b/tests/video/AndroidTest.xml
index e30a394..3836f98 100644
--- a/tests/video/AndroidTest.xml
+++ b/tests/video/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS Video test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="media" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/vm/AndroidTest.xml b/tests/vm/AndroidTest.xml
index c6f2058..4035349 100644
--- a/tests/vm/AndroidTest.xml
+++ b/tests/vm/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS VM test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/vr/Android.mk b/tests/vr/Android.mk
index 74aebf1..56a7310 100644
--- a/tests/vr/Android.mk
+++ b/tests/vr/Android.mk
@@ -27,7 +27,9 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_JNI_SHARED_LIBRARIES := libctsvrextensions_jni libnativehelper_compat_libc++
diff --git a/tools/cts-api-coverage/Android.mk b/tools/cts-api-coverage/Android.mk
index c66f7d4..7b56e21 100644
--- a/tools/cts-api-coverage/Android.mk
+++ b/tools/cts-api-coverage/Android.mk
@@ -14,23 +14,51 @@
LOCAL_PATH := $(call my-dir)
-# the hat script
+# the cts-api-coverage script
# ============================================================
include $(CLEAR_VARS)
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE := cts-api-coverage
-LOCAL_STATIC_JAVA_LIBRARIES := \
- compatibility-common-util-devicesidelib
LOCAL_SRC_FILES := etc/$(LOCAL_MODULE)
LOCAL_ADDITIONAL_DEPENDENCIES := $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE)$(COMMON_JAVA_PACKAGE_SUFFIX)
include $(BUILD_PREBUILT)
-# the other stuff
+# the ndk-api-report script
# ============================================================
-subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
- src \
- ))
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := ndk-api-report
+LOCAL_SRC_FILES := etc/$(LOCAL_MODULE)
-include $(subdirs)
+include $(BUILD_PREBUILT)
+
+# cts-api-coverage java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ $(call all-subdir-java-files) \
+ $(call all-proto-files-under, proto)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ compatibility-host-util \
+ dexlib2
+
+LOCAL_MODULE := cts-api-coverage
+
+# This tool is not checking any dependencies or metadata, so all of the
+# dependencies of all of the tests must be on its classpath. This is
+# super fragile.
+LOCAL_STATIC_JAVA_LIBRARIES += \
+ platformprotos
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/MANIFEST.mf b/tools/cts-api-coverage/MANIFEST.mf
similarity index 100%
rename from tools/cts-api-coverage/src/MANIFEST.mf
rename to tools/cts-api-coverage/MANIFEST.mf
diff --git a/tools/cts-api-coverage/proto/testsuite.proto b/tools/cts-api-coverage/proto/testsuite.proto
new file mode 100644
index 0000000..a0adb6f
--- /dev/null
+++ b/tools/cts-api-coverage/proto/testsuite.proto
@@ -0,0 +1,103 @@
+// Copyright (C) 2018 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.
+
+// [START declaration]
+syntax = "proto3";
+package com_android_cts_apicoverage;
+// [END declaration]
+
+// [START java_declaration]
+option java_package = "com.android.cts.apicoverage";
+option java_outer_classname = "TestSuiteProto";
+// [END java_declaration]
+
+// [START messages]
+message Option {
+ string name = 1;
+ string key = 2;
+ string value =3;
+}
+
+message ConfigMetadata {
+ string module_name = 1;
+ string component = 2;
+ repeated Option options = 3;
+
+ message TargetPreparer {
+ string test_class = 1;
+ repeated Option options = 2;
+ }
+ repeated TargetPreparer target_preparers = 4;
+
+ message TestClass {
+ string test_class = 1;
+ string package = 2;
+ repeated Option options = 3;
+ }
+ repeated TestClass test_classes = 5;
+}
+
+// target File Metadata for e.g. config, apk, jar, exe, so
+message FileMetadata {
+ string description = 1;
+ ConfigMetadata config_metadata = 2;
+}
+
+// An entry in a Test Suire Release messages: cts, etc.
+message Entry {
+ // Entry ID
+ string id = 1;
+ // Name
+ string name = 2;
+
+ enum EntryType {
+ FOLDER = 0;
+ FILE = 1;
+ CONFIG = 2;
+ JAR = 3;
+ APK = 4;
+ EXE = 5;
+ SO = 6;
+ }
+
+ // Type
+ EntryType type = 3;
+ // Size
+ int64 size = 4;
+ // Content ID
+ string content_id = 5;
+ // Parent entry ID
+ string parent_id = 6;
+ // Relative path
+ string relative_path = 7;
+
+ FileMetadata file_metadata = 8;
+}
+
+// Test Suite Release: cts, etc.
+message TestSuiteContent {
+ // Entry ID
+ string id = 1;
+ // Name
+ string name = 2;
+ // Version
+ string version = 3;
+ // Build ID
+ string bid = 4;
+ // Content ID
+ string content_id = 5;
+ // File Entries
+ repeated Entry file_entries = 6;
+}
+// [END messages]
diff --git a/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/res/api-coverage.xsl
similarity index 100%
rename from tools/cts-api-coverage/src/res/api-coverage.xsl
rename to tools/cts-api-coverage/res/api-coverage.xsl
diff --git a/tools/cts-api-coverage/src/Android.mk b/tools/cts-api-coverage/src/Android.mk
deleted file mode 100644
index fcf7ad4..0000000
--- a/tools/cts-api-coverage/src/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2010 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)
-
-
-# cts-api-coverage java library
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- compatibility-host-util \
- dexlib2
-
-LOCAL_MODULE := cts-api-coverage
-
-# This tool is not checking any dependencies or metadata, so all of the
-# dependencies of all of the tests must be on its classpath. This is
-# super fragile.
-LOCAL_STATIC_JAVA_LIBRARIES += \
- platformprotos
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java
index a1a6923..3c88938 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/GTestApiReport.java
@@ -128,7 +128,7 @@
for (File testConfigFile : testConfigFiles) {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
- TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler();
+ TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler(file.getName());
xmlReader.setContentHandler(testModuleXmlHandler);
FileReader fileReader = null;
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java
index 85423b7..8a17153 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestModuleConfigHandler.java
@@ -16,6 +16,8 @@
package com.android.cts.apicoverage;
+import com.android.cts.apicoverage.TestSuiteProto.*;
+
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
@@ -25,25 +27,65 @@
* TestModule.xml.
*/
class TestModuleConfigHandler extends DefaultHandler {
- private String mTestClassName;
- private String mModuleName;
- private Boolean inTestEle = false;
+ private static final String CONFIGURATION_TAG = "configuration";
+ private static final String DESCRIPTION_TAG = "description";
+ private static final String OPTION_TAG = "option";
+ private static final String TARGET_PREPARER_TAG = "target_preparer";
+ private static final String TEST_TAG = "test";
+ private static final String CLASS_TAG = "class";
+ private static final String NAME_TAG = "name";
+ private static final String KEY_TAG = "key";
+ private static final String VALUE_TAG = "value";
+ private static final String MODULE_NAME_TAG = "module-name";
+ private static final String GTEST_CLASS_TAG = "com.android.tradefed.testtype.GTest";
+
+ private FileMetadata.Builder mFileMetadata;
+ private ConfigMetadata.Builder mConfigMetadata;
+ private ConfigMetadata.TestClass.Builder mTestCase;
+ private ConfigMetadata.TargetPreparer.Builder mTargetPreparer;
+ private String mModuleName = null;
+
+ TestModuleConfigHandler(String configFileName) {
+ mFileMetadata = FileMetadata.newBuilder();
+ mConfigMetadata = ConfigMetadata.newBuilder();
+ mTestCase = null;
+ mTargetPreparer = null;
+ // Default Module Name is the Config File Name
+ mModuleName = configFileName.replaceAll(".config$", "");
+ }
@Override
public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException {
super.startElement(uri, localName, name, attributes);
- if ("test".equalsIgnoreCase(localName)) {
- mTestClassName = attributes.getValue("class");
- inTestEle = true;
- } else if ("option".equalsIgnoreCase(localName)) {
- if (inTestEle) {
- String optName = attributes.getValue("name");
- if ("module-name".equalsIgnoreCase(optName)) {
- mModuleName = attributes.getValue("value");
+ if (CONFIGURATION_TAG.equalsIgnoreCase(localName)) {
+ if (null != attributes.getValue(DESCRIPTION_TAG)) {
+ mFileMetadata.setDescription(attributes.getValue(DESCRIPTION_TAG));
+ } else {
+ mFileMetadata.setDescription("WARNING: no description.");
+ }
+ } else if (TEST_TAG.equalsIgnoreCase(localName)) {
+ mTestCase = ConfigMetadata.TestClass.newBuilder();
+ mTestCase.setTestClass(attributes.getValue(CLASS_TAG));
+ } else if (TARGET_PREPARER_TAG.equalsIgnoreCase(localName)) {
+ mTargetPreparer = ConfigMetadata.TargetPreparer.newBuilder();
+ mTargetPreparer.setTestClass(attributes.getValue(CLASS_TAG));
+ } else if (OPTION_TAG.equalsIgnoreCase(localName)) {
+ Option.Builder option = Option.newBuilder();
+ option.setName(attributes.getValue(NAME_TAG));
+ option.setValue(attributes.getValue(VALUE_TAG));
+ String keyStr = attributes.getValue(KEY_TAG);
+ if (null != keyStr) {
+ option.setKey(keyStr);
+ }
+ if (null != mTestCase) {
+ mTestCase.addOptions(option);
+ if (GTEST_CLASS_TAG.equalsIgnoreCase(option.getName())) {
+ mModuleName = option.getValue();
}
- //System.out.println(String.format("%s: %s, %s, %s", localName, name, optName, attributes.getValue("value")));
+ } else if (null != mTargetPreparer) {
+ mTargetPreparer.addOptions(option);
}
}
}
@@ -51,8 +93,14 @@
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
super.endElement(uri, localName, name);
- if ("test".equalsIgnoreCase(localName)) {
- inTestEle = false;
+ if (TEST_TAG.equalsIgnoreCase(localName)) {
+ mConfigMetadata.addTestClasses(mTestCase);
+ mTestCase = null;
+ } else if (TARGET_PREPARER_TAG.equalsIgnoreCase(localName)) {
+ mConfigMetadata.addTargetPreparers(mTargetPreparer);
+ mTargetPreparer = null;
+ } else if (CONFIGURATION_TAG.equalsIgnoreCase(localName)) {
+ mFileMetadata.setConfigMetadata(mConfigMetadata);
}
}
@@ -61,6 +109,11 @@
}
public String getTestClassName() {
- return mTestClassName;
+ //return the 1st Test Class
+ return mFileMetadata.getConfigMetadata().getTestClassesList().get(0).getTestClass();
+ }
+
+ public FileMetadata getFileMetadata() {
+ return mFileMetadata.build();
}
}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
new file mode 100644
index 0000000..044acdc
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2018 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.apicoverage;
+
+
+import com.android.cts.apicoverage.TestSuiteProto.*;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+
+class TestSuiteContentReport {
+ // configuration option
+ private static final String NOT_SHARDABLE_TAG = "not-shardable";
+ // test class option
+ private static final String RUNTIME_HIT_TAG = "runtime-hint";
+ // com.android.tradefed.testtype.AndroidJUnitTest option
+ private static final String PACKAGE_TAG = "package";
+ // com.android.compatibility.common.tradefed.testtype.JarHostTest option
+ private static final String JAR_NAME_TAG = "jar";
+ // com.android.tradefed.testtype.GTest option
+ private static final String NATIVE_TEST_DEVICE_PATH_TAG = "native-test-device-path";
+ private static final String MODULE_TAG = "module-name";
+
+ private static final String SUITE_API_INSTALLER_TAG = "com.android.tradefed.targetprep.suite.SuiteApkInstaller";
+ private static final String JAR_HOST_TEST_TAG = "com.android.compatibility.common.tradefed.testtype.JarHostTest";
+ // com.android.tradefed.targetprep.suite.SuiteApkInstaller option
+ private static final String TEST_FILE_NAME_TAG = "test-file-name";
+ // com.android.compatibility.common.tradefed.targetprep.FilePusher option
+ private static final String PUSH_TAG = "push";
+
+ // Target File Extensions
+ private static final String CONFIG_EXT_TAG = ".config";
+ private static final String JAR_EXT_TAG = ".jar";
+ private static final String APK_EXT_TAG = ".apk";
+ private static final String SO_EXT_TAG = ".so";
+
+ private static void printUsage() {
+ System.out.println("Usage: test-suite-content-report [OPTION]...");
+ System.out.println();
+ System.out.println("Generates test suite content protocal buffer message.");
+ System.out.println();
+ System.out.println(
+ "$ANDROID_HOST_OUT/bin/test-suite-content-report "
+ + "-i out/host/linux-x86/cts/android-cts "
+ + "-o ./cts-content.pb");
+ System.out.println();
+ System.out.println("Options:");
+ System.out.println(" -o FILE output file or standard out if not given");
+ System.out.println(" -i PATH path to the Test Suite Folder");
+ System.out.println();
+ System.exit(1);
+ }
+
+ /** Get the argument or print out the usage and exit. */
+ private static String getExpectedArg(String[] args, int index) {
+ if (index < args.length) {
+ return args[index];
+ } else {
+ printUsage();
+ return null; // Never will happen because printUsage will call exit(1)
+ }
+ }
+
+ public static TestSuiteContent parseTestSuiteFolder(String testSuitePath)
+ throws IOException, NoSuchAlgorithmException {
+
+ TestSuiteContent.Builder testSuiteContent = TestSuiteContent.newBuilder();
+ testSuiteContent.addFileEntries(parseFolder(testSuiteContent, testSuitePath, testSuitePath));
+ return testSuiteContent.build();
+ }
+
+ // Parse a file
+ private static FileMetadata parseFileMetadata(Entry.Builder fEntry, File file)
+ throws Exception {
+ if (file.getName().endsWith(CONFIG_EXT_TAG)) {
+ fEntry.setType(Entry.EntryType.CONFIG);
+ return parseConfigFile(file);
+ } else if (file.getName().endsWith(APK_EXT_TAG)) {
+ fEntry.setType(Entry.EntryType.APK);
+ } else if (file.getName().endsWith(JAR_EXT_TAG)) {
+ fEntry.setType(Entry.EntryType.JAR);
+ } else if (file.getName().endsWith(SO_EXT_TAG)) {
+ fEntry.setType(Entry.EntryType.SO);
+ } else {
+ // Just file in general
+ fEntry.setType(Entry.EntryType.FILE);
+ }
+ return null;
+ }
+
+ private static FileMetadata parseConfigFile(File file)
+ throws Exception {
+ XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+ TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler(file.getName());
+ xmlReader.setContentHandler(testModuleXmlHandler);
+ FileReader fileReader = null;
+ try {
+ fileReader = new FileReader(file);
+ xmlReader.parse(new InputSource(fileReader));
+ return testModuleXmlHandler.getFileMetadata();
+ } finally {
+ if (null != fileReader) {
+ fileReader.close();
+ }
+ }
+ }
+
+ // Parse a folder to add all entries
+ private static Entry.Builder parseFolder(TestSuiteContent.Builder testSuiteContent, String fPath, String rPath)
+ throws IOException, NoSuchAlgorithmException {
+ Entry.Builder folderEntry = Entry.newBuilder();
+
+ File folder = new File(fPath);
+ File rFolder = new File(rPath);
+ Path folderPath = Paths.get(folder.getAbsolutePath());
+ Path rootPath = Paths.get(rFolder.getAbsolutePath());
+ String folderRelativePath = rootPath.relativize(folderPath).toString();
+ String folderId = getId(folderRelativePath);
+ File[] fileList = folder.listFiles();
+ Long folderSize = 0L;
+ List <Entry> entryList = new ArrayList<Entry> ();
+ for (File file : fileList){
+ if (file.isFile()){
+ String fileRelativePath = rootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
+ Entry.Builder fileEntry = Entry.newBuilder();
+ fileEntry.setId(getId(fileRelativePath));
+ fileEntry.setName(file.getName());
+ fileEntry.setSize(file.length());
+ fileEntry.setContentId(getFileContentId(file));
+ fileEntry.setRelativePath(fileRelativePath);
+ fileEntry.setParentId(folderId);
+ try {
+ FileMetadata fMetadata = parseFileMetadata(fileEntry, file);
+ if (null != fMetadata) {
+ fileEntry.setFileMetadata(fMetadata);
+ }
+ } catch (Exception ex) {
+ System.err.println(
+ String.format("Cannot parse %s",
+ file.getAbsolutePath()));
+ ex.printStackTrace();
+ }
+ testSuiteContent.addFileEntries(fileEntry);
+ entryList.add(fileEntry.build());
+ folderSize += file.length();
+ } else if (file.isDirectory()){
+ Entry.Builder subFolderEntry = parseFolder(testSuiteContent, file.getAbsolutePath(), rPath);
+ subFolderEntry.setParentId(folderId);
+ testSuiteContent.addFileEntries(subFolderEntry);
+ folderSize += subFolderEntry.getSize();
+ entryList.add(subFolderEntry.build());
+ }
+ }
+
+ folderEntry.setId(folderId);
+ folderEntry.setName(folderRelativePath);
+ folderEntry.setSize(folderSize);
+ folderEntry.setType(Entry.EntryType.FOLDER);
+ folderEntry.setContentId(getFolderContentId(folderEntry, entryList));
+ folderEntry.setRelativePath(folderRelativePath);
+ return folderEntry;
+ }
+
+ private static String getFileContentId(File file)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ FileInputStream fis = new FileInputStream(file);
+
+ byte[] dataBytes = new byte[10240];
+
+ int nread = 0;
+ while ((nread = fis.read(dataBytes)) != -1) {
+ md.update(dataBytes, 0, nread);
+ };
+ byte[] mdbytes = md.digest();
+
+ // Converts to Hex String
+ StringBuffer hexString = new StringBuffer();
+ for (int i=0;i<mdbytes.length;i++) {
+ hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+ }
+ return hexString.toString();
+ }
+
+ private static String getFolderContentId(Entry.Builder folderEntry, List<Entry> entryList)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+ for (Entry entry: entryList) {
+ md.update(entry.getContentId().getBytes(StandardCharsets.UTF_8));
+ }
+ byte[] mdbytes = md.digest();
+
+ // Converts to Hex String
+ StringBuffer hexString = new StringBuffer();
+ for (int i=0;i<mdbytes.length;i++) {
+ hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+ }
+ return hexString.toString();
+ }
+
+ private static String getId(String name)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ md.update(name.getBytes(StandardCharsets.UTF_8));
+ byte[] mdbytes = md.digest();
+
+ // Converts to Hex String
+ StringBuffer hexString = new StringBuffer();
+ for (int i=0;i<mdbytes.length;i++) {
+ hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+ }
+ return hexString.toString();
+ }
+
+ // Iterates though all test suite content and prints them.
+ static void Print(TestSuiteContent tsContent) {
+ //Header
+ System.out.printf("no,type,name,size,relative path,id,content id,parent id,description,test class");
+ // test class header
+ System.out.printf(",%s,%s,%s,%s,%s",
+ RUNTIME_HIT_TAG, PACKAGE_TAG, JAR_NAME_TAG, NATIVE_TEST_DEVICE_PATH_TAG, MODULE_TAG);
+ // target preparer header
+ System.out.printf(",%s,%s\n",
+ TEST_FILE_NAME_TAG, PUSH_TAG);
+
+ int i = 1;
+ for (Entry entry: tsContent.getFileEntriesList()) {
+ System.out.printf("%d,%s,%s,%d,%s,%s,%s,%s",
+ i++, entry.getType(), entry.getName(), entry.getSize(),
+ entry.getRelativePath(), entry.getId(), entry.getContentId(),
+ entry.getParentId());
+ if (Entry.EntryType.CONFIG == entry.getType()) {
+ ConfigMetadata config = entry.getFileMetadata().getConfigMetadata();
+ System.out.printf(",%s", entry.getFileMetadata().getDescription());
+ List<Option> optList;
+ List<ConfigMetadata.TestClass> testClassesList = config.getTestClassesList();
+ String rtHit = "";
+ String pkg = "";
+ String jar = "";
+ String ntdPath = "";
+ String module = "";
+
+ for (ConfigMetadata.TestClass tClass : testClassesList) {
+ System.out.printf(",%s", tClass.getTestClass());
+ optList = tClass.getOptionsList();
+ for (Option opt : optList) {
+ if (RUNTIME_HIT_TAG.equalsIgnoreCase(opt.getName())) {
+ rtHit = rtHit + opt.getValue() + " ";
+ } else if (PACKAGE_TAG.equalsIgnoreCase(opt.getName())) {
+ pkg = pkg + opt.getValue() + " ";
+ } else if (JAR_NAME_TAG.equalsIgnoreCase(opt.getName())) {
+ jar = jar + opt.getValue() + " ";
+ } else if (NATIVE_TEST_DEVICE_PATH_TAG.equalsIgnoreCase(opt.getName())) {
+ ntdPath = ntdPath + opt.getValue() + " ";
+ } else if (MODULE_TAG.equalsIgnoreCase(opt.getName())) {
+ module = module + opt.getValue() + " ";
+ }
+ }
+ }
+ System.out.printf(",%s,%s,%s,%s,%s", rtHit.trim(), pkg.trim(),
+ jar.trim(), module.trim(), ntdPath.trim());
+
+ List<ConfigMetadata.TargetPreparer> tPrepList = config.getTargetPreparersList();
+ String testFile = "";
+ String pushList = "";
+ for (ConfigMetadata.TargetPreparer tPrep : tPrepList) {
+ optList = tPrep.getOptionsList();
+ for (Option opt : optList) {
+ if (TEST_FILE_NAME_TAG.equalsIgnoreCase(opt.getName())) {
+ testFile = testFile + opt.getValue() + " ";
+ } else if (PUSH_TAG.equalsIgnoreCase(opt.getName())) {
+ pushList = pushList + opt.getValue() + " ";
+ }
+ }
+ }
+ System.out.printf(",%s,%s", testFile.trim(), pushList.trim());
+ }
+ System.out.printf("\n");
+ }
+ }
+
+ public static void main(String[] args)
+ throws IOException, NoSuchAlgorithmException {
+ String outputFilePath = "./tsContentMessage.pb";
+ String tsPath = "";
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].startsWith("-")) {
+ if ("-o".equals(args[i])) {
+ outputFilePath = getExpectedArg(args, ++i);
+ } else if ("-i".equals(args[i])) {
+ tsPath = getExpectedArg(args, ++i);
+ File file = new File(tsPath);
+ if (file.isDirectory()) {
+ //
+ } else {
+ printUsage();
+ }
+ } else {
+ printUsage();
+ }
+ }
+ }
+
+ TestSuiteContent tsContent = parseTestSuiteFolder(tsPath);
+
+ // Write test suite content message to disk.
+ FileOutputStream output = new FileOutputStream(outputFilePath);
+ try {
+ tsContent.writeTo(output);
+ } finally {
+ output.close();
+ }
+
+ // Read message from the file and print them out
+ TestSuiteContent tsContent1 =
+ TestSuiteContent.parseFrom(new FileInputStream(outputFilePath));
+ Print(tsContent1);
+ }
+}
diff --git a/tools/cts-device-info/Android.mk b/tools/cts-device-info/Android.mk
index 2f11db9..328b0a3 100644
--- a/tools/cts-device-info/Android.mk
+++ b/tools/cts-device-info/Android.mk
@@ -18,6 +18,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LIBRARIES := android.test.base
+
LOCAL_JNI_SHARED_LIBRARIES := libctsdeviceinfo
LOCAL_MULTILIB := both
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index 41afdb9..782e075 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -518,6 +518,7 @@
charsKeyNames.add(CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS.getName());
charsKeyNames.add(CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES.getName());
charsKeyNames.add(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL.getName());
+ charsKeyNames.add(CameraCharacteristics.INFO_VERSION.getName());
charsKeyNames.add(CameraCharacteristics.SYNC_MAX_LATENCY.getName());
charsKeyNames.add(CameraCharacteristics.REPROCESS_MAX_CAPTURE_STALL.getName());
charsKeyNames.add(CameraCharacteristics.DEPTH_DEPTH_IS_EXCLUSIVE.getName());
diff --git a/tools/cts-holo-generation/Android.mk b/tools/cts-holo-generation/Android.mk
index 43b346d..195f5a5 100644
--- a/tools/cts-holo-generation/Android.mk
+++ b/tools/cts-holo-generation/Android.mk
@@ -23,7 +23,9 @@
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tools/cts-preconditions/Android.mk b/tools/cts-preconditions/Android.mk
index 76bfaa8..5f00c36 100644
--- a/tools/cts-preconditions/Android.mk
+++ b/tools/cts-preconditions/Android.mk
@@ -27,8 +27,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
- compatibility-device-preconditions \
- legacy-android-test
+ compatibility-device-preconditions
+
+LOCAL_JAVA_LIBRARIES := android.test.base.stubs
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tools/cts-reference-app-lib/Android.mk b/tools/cts-reference-app-lib/Android.mk
index 8f6039d..f006bb0 100644
--- a/tools/cts-reference-app-lib/Android.mk
+++ b/tools/cts-reference-app-lib/Android.mk
@@ -24,7 +24,9 @@
# and when built explicitly put it in the data partition
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
LOCAL_SDK_VERSION := current
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index 9f0256c..ffbd6b1 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -23,8 +23,8 @@
<option name="compatibility:exclude-filter" value="CtsLocationTestCases android.location.cts.GnssTtffTests#testTtffWithNetwork" />
<!-- b/35314835 -->
- <option name="compatibility:exclude-filter" value="CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests#testAlwaysFocusablePipActivity" />
- <option name="compatibility:exclude-filter" value="CtsServicesHostTestCases android.server.cts.ActivityManagerPinnedStackTests#testLaunchIntoPinnedStack" />
+ <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerPinnedStackTests#testAlwaysFocusablePipActivity" />
+ <option name="compatibility:exclude-filter" value="CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerPinnedStackTests#testLaunchIntoPinnedStack" />
<!-- b/17595050 -->
<option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText" />
@@ -126,6 +126,12 @@
<!-- b/36686383 -->
<option name="compatibility:exclude-filter" value="CtsIncidentHostTestCases com.android.server.cts.ErrorsTest#testANR" />
+ <!-- b/33090965 -->
+ <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf0320x0180" />
+ <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf0640x0360" />
+ <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf1280x0720" />
+ <option name="compatibility:exclude-filter" value="CtsVideoTestCases android.video.cts.VideoEncoderDecoderTest#testVp9Goog0Perf1920x1080" />
+
<!-- b/37482372 -->
<option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowHistoryPreservePortraitTest" />
<option name="compatibility:exclude-filter" value="CtsPreference2TestCases android.preference2.cts.PreferenceActivityFlowPortraitTest#multiWindowInOutPortraitTest" />
@@ -175,6 +181,9 @@
<option name="compatibility:exclude-filter" value="x86_64 CtsMediaBitstreamsTestCases" />
<option name="compatibility:exclude-filter" value="mips64 CtsMediaBitstreamsTestCases" />
+ <!-- b/38280830 -->
+ <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testVp8Goog0Perf1280x0720" />
+
<!-- b/38420898 -->
<option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyAccelMultiChannel" />
<option name="compatibility:exclude-filter" value="CtsSensorTestCases android.hardware.cts.SensorDirectReportTest#testRateIndependencyGyroMultiChannel" />
diff --git a/tools/cts-tradefed/res/config/retry.xml b/tools/cts-tradefed/res/config/retry.xml
index cbecada..e488410 100644
--- a/tools/cts-tradefed/res/config/retry.xml
+++ b/tools/cts-tradefed/res/config/retry.xml
@@ -18,6 +18,10 @@
<device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
<build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
<test class="com.android.compatibility.common.tradefed.testtype.retry.RetryFactoryTest" />
+
+ <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
+ <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
+
<logger class="com.android.tradefed.log.FileLogger">
<option name="log-level-display" value="WARN" />
</logger>
diff --git a/tools/vm-tests-tf/Android.mk b/tools/vm-tests-tf/Android.mk
index 70c74e1..ea4f09b 100644
--- a/tools/vm-tests-tf/Android.mk
+++ b/tools/vm-tests-tf/Android.mk
@@ -25,7 +25,6 @@
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LIBRARIES := junit
--include cts/error_prone_rules_tests.mk
include $(BUILD_JAVA_LIBRARY)
cts-tf-dalvik-lib.jar := $(full_classes_jar)
diff --git a/tools/vm-tests-tf/AndroidTest.xml b/tools/vm-tests-tf/AndroidTest.xml
index 72b3914..fc0f8af 100644
--- a/tools/vm-tests-tf/AndroidTest.xml
+++ b/tools/vm-tests-tf/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for CTS VM test cases">
+ <option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="art" />
<option name="not-strict-shardable" value= "true" />
<target_preparer class="android.core.vm.targetprep.VmTestPreparer" />